first init

This commit is contained in:
ISMAIL MASSERAN
2026-09-01 10:12:31 +08:00
commit ca6e55f853
718 changed files with 204746 additions and 0 deletions
@@ -0,0 +1,339 @@
"""ATR class managing some of the Answer To Reset content.
__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 __future__ import annotations
import functools
import operator
import warnings
from smartcard.Exceptions import SmartcardException
class ATR:
"""Parse and represent Answer to Reset sequences.
Answer to Reset sequences are defined in ISO 7816-3, section 8.
"""
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-public-methods
clockrateconversion: list[int | str] = [
372,
372,
558,
744,
1116,
1488,
1860,
"RFU",
"RFU",
512,
768,
1024,
1536,
2048,
"RFU",
"RFU",
]
bitratefactor: list[int | str] = [
"RFU",
1,
2,
4,
8,
16,
32,
64,
12,
20,
"RFU",
"RFU",
"RFU",
"RFU",
"RFU",
"RFU",
]
currenttable: list[int | str] = [25, 50, 100, "RFU"]
def __init__(self, atr: list[int]) -> None:
"""Parse ATR and initialize members:
- TS: initial character
- T0: format character
- TA[n], TB[n], TC[n], TD[n], for n=0,1,...: protocol parameters
@note: protocol parameters indices start at 0, e.g.
TA[0], TA[1] correspond to the ISO standard TA1, TA2
parameters
- historicalBytes: the ATR T1, T2, ..., TK historical bytes
- TCK: checksum byte (only for protocols different from T=0)
- FI: clock rate conversion factor
- DI: voltage adjustment factor
- PI1: programming voltage factor
- II: maximum programming current factor
- N: extra guard time
"""
# pylint: disable=too-many-statements
if len(atr) < 2:
raise SmartcardException("ATR sequences must be at least 2 bytes long")
if atr[0] not in {0x3B, 0x3F}:
raise SmartcardException(f"invalid TS 0x{atr[0]:02x}")
self.atr = atr
# initial character
self.TS = self.atr[0]
# format character
self.T0 = self.atr[1]
# count of historical bytes
self.K = self.T0 & 0x0F
# initialize optional characters lists
self.TA: list[None | int] = []
self.TB: list[None | int] = []
self.TC: list[None | int] = []
self.TD: list[None | int] = []
self.Y: list[int] = []
td: None | int = self.T0
offset = 1
while td is not None:
self.Y.append(td >> 4 & 0x0F)
self.TA += [None]
self.TB += [None]
self.TC += [None]
self.TD += [None]
if self.Y[-1] & 0x01: # TA
offset += 1
self.TA[-1] = self.atr[offset]
if self.Y[-1] & 0x02: # TB
offset += 1
self.TB[-1] = self.atr[offset]
if self.Y[-1] & 0x04: # TC
offset += 1
self.TC[-1] = self.atr[offset]
if self.Y[-1] & 0x08: # TD
offset += 1
self.TD[-1] = self.atr[offset]
td = self.TD[-1]
self.interfaceBytesCount = offset - 1
# historical bytes
self.historicalBytes = self.atr[offset + 1 : offset + 1 + self.K]
# checksum
self.TCK: int | None = None
self.checksumOK: bool | None = None
self.hasChecksum = len(self.atr) == offset + 1 + self.K + 1
if self.hasChecksum:
self.TCK = self.atr[-1]
self.checksumOK = functools.reduce(operator.xor, self.atr[1:]) == 0
# clock-rate conversion factor
self.FI: int | None = None
if self.TA[0] is not None:
self.FI = self.TA[0] >> 4 & 0x0F
# bit-rate adjustment factor
self.DI: int | None = None
if self.TA[0] is not None:
self.DI = self.TA[0] & 0x0F
# maximum programming current factor
self.II: int | None = None
if self.TB[0] is not None:
self.II = self.TB[0] >> 5 & 0x03
# programming voltage factor
self.PI1: int | None = None
if self.TB[0] is not None:
self.PI1 = self.TB[0] & 0x1F
# extra guard time
self.N = self.TC[0]
@property
def hasTA(self) -> list[bool]:
"""Deprecated. Replace usage with `ATR.TA[i] is not None`."""
warnings.warn("Replace usage with `ATR.TA[i] is not None`", DeprecationWarning)
return [ta is not None for ta in self.TA]
@property
def hasTB(self) -> list[bool]:
"""Deprecated. Replace usage with `ATR.TB[i] is not None`."""
warnings.warn("Replace usage with `ATR.TB[i] is not None`", DeprecationWarning)
return [tb is not None for tb in self.TB]
@property
def hasTC(self) -> list[bool]:
"""Deprecated. Replace usage with `ATR.TC[i] is not None`."""
warnings.warn("Replace usage with `ATR.TC[i] is not None`", DeprecationWarning)
return [tc is not None for tc in self.TC]
@property
def hasTD(self) -> list[bool]:
"""Deprecated. Replace usage with `ATR.TD[i] is not None`."""
warnings.warn("Replace usage with `ATR.TD[i] is not None`", DeprecationWarning)
return [td is not None for td in self.TD]
def getChecksum(self) -> int | None:
"""Return the checksum of the ATR. Checksum is mandatory only
for T=1."""
return self.TCK
def getHistoricalBytes(self) -> list[int]:
"""Return historical bytes."""
return self.historicalBytes
def getHistoricalBytesCount(self) -> int:
"""Return count of historical bytes."""
return len(self.historicalBytes)
def getInterfaceBytesCount(self) -> int:
"""Return count of interface bytes."""
return self.interfaceBytesCount
def getTA1(self) -> int | None:
"""Return TA1 byte."""
return self.TA[0]
def getTB1(self) -> int | None:
"""Return TB1 byte."""
return self.TB[0]
def getTC1(self) -> int | None:
"""Return TC1 byte."""
return self.TC[0]
def getTD1(self) -> int | None:
"""Return TD1 byte."""
return self.TD[0]
def getBitRateFactor(self) -> int | str:
"""Return bit rate factor."""
if self.DI is not None:
return ATR.bitratefactor[self.DI]
return 1
def getClockRateConversion(self) -> int | str:
"""Return clock rate conversion."""
if self.FI is not None:
return ATR.clockrateconversion[self.FI]
return 372
def getProgrammingCurrent(self) -> int | str:
"""Return maximum programming current."""
if self.II is not None:
return ATR.currenttable[self.II]
return 50
def getProgrammingVoltage(self) -> int:
"""Return programming voltage."""
if self.PI1 is not None:
return 5 * (1 + self.PI1)
return 5
def getGuardTime(self) -> int | None:
"""Return extra guard time."""
return self.N
def getSupportedProtocols(self) -> dict[str, bool]:
"""Returns a dictionary of supported protocols."""
protocols: dict[str, bool] = {}
for td in self.TD:
if td is not None:
protocols[f"T={td & 0x0F}"] = True
if self.TD[0] is None:
protocols["T=0"] = True
return protocols
def isT0Supported(self) -> bool:
"""Return True if T=0 is supported."""
return "T=0" in self.getSupportedProtocols()
def isT1Supported(self) -> bool:
"""Return True if T=1 is supported."""
return "T=1" in self.getSupportedProtocols()
def isT15Supported(self) -> bool:
"""Return True if T=15 is supported."""
return "T=15" in self.getSupportedProtocols()
def render(self) -> str:
"""Render the ATR to a readable format."""
lines: list[str] = []
enumerated_tx_values = enumerate(zip(self.TA, self.TB, self.TC, self.TD), 1)
for i, (ta, tb, tc, td) in enumerated_tx_values:
if ta is not None:
lines.append(f"TA{i}: {ta:x}")
if tb is not None:
lines.append(f"TB{i}: {tb:x}")
if tc is not None:
lines.append(f"TC{i}: {tc:x}")
if td is not None:
lines.append(f"TD{i}: {td:x}")
lines.append(f"supported protocols {','.join(self.getSupportedProtocols())}")
lines.append(f"T=0 supported: {self.isT0Supported()}")
lines.append(f"T=1 supported: {self.isT1Supported()}")
if self.getChecksum() is not None:
lines.append(f"checksum: {self.getChecksum()}")
lines.append(f"\tclock rate conversion factor: {self.getClockRateConversion()}")
lines.append(f"\tbit rate adjustment factor: {self.getBitRateFactor()}")
lines.append(f"\tmaximum programming current: {self.getProgrammingCurrent()}")
lines.append(f"\tprogramming voltage: {self.getProgrammingVoltage()}")
lines.append(f"\tguard time: {self.getGuardTime()}")
lines.append(f"nb of interface bytes: {self.getInterfaceBytesCount()}")
lines.append(f"nb of historical bytes: {self.getHistoricalBytesCount()}")
return "\n".join(lines)
def dump(self) -> None:
"""Deprecated. Replace usage with `print(ATR.render())`"""
warnings.warn("Replace usage with `print(ATR.render())`", DeprecationWarning)
print(self.render())
def __str__(self) -> str:
"""Render the ATR as a space-separated string of uppercase hexadecimal pairs."""
return bytes(self.atr).hex(" ").upper()
@@ -0,0 +1,93 @@
"""AbstractCardRequest class.
__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 smartcard.System
from smartcard.CardType import AnyCardType
from smartcard.PassThruCardService import PassThruCardService
class AbstractCardRequest:
"""The base class for xxxCardRequest classes.
A CardRequest is used for waitForCard() invocations and specifies what
kind of smart card an application is waited for."""
# 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 CardRequest.
@param newcardonly: if True, request a new card; default is
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{smartcard.CardType.CardType} to wait for;
default is L{smartcard.CardType.AnyCardType},
i.e. the request will succeed with any card
@param cardServiceClass: the specific card service class to create
and bind to the card;default is to create
and bind a L{smartcard.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 None
"""
self.newcardonly = newcardonly
self.readersAsked = readers
self.cardType = cardType
self.cardServiceClass = cardServiceClass
self.timeout = timeout
# if no CardType requested, use AnyCardType
if self.cardType is None:
self.cardType = AnyCardType()
# if no card service requested, use pass-thru card service
if self.cardServiceClass is None:
self.cardServiceClass = PassThruCardService
def getReaders(self):
"""Returns the list or readers on which to wait for cards."""
# if readers not given, use all readers
if self.readersAsked is None:
return smartcard.System.readers()
return self.readersAsked
def waitforcard(self):
"""Wait for card insertion and returns a card service."""
def waitforcardevent(self):
"""Wait for card insertion or removal."""
@@ -0,0 +1,77 @@
"""Card class.
__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.reader.Reader import Reader
from smartcard.System import readers
from smartcard.util import toHexString
class Card:
"""Card class."""
def __init__(self, reader, atr):
"""Card constructor.
@param reader: reader in which the card is inserted
@param atr: ATR of the card"""
self.reader = reader
self.atr = atr
def __repr__(self):
"""Return a string representing the Card (atr and reader
concatenation)."""
return toHexString(self.atr) + " / " + str(self.reader)
def __eq__(self, other):
"""Return True if self==other (same reader and same atr).
Return False otherwise."""
if isinstance(other, Card):
return self.atr == other.atr and repr(self.reader) == repr(other.reader)
return False
def __ne__(self, other):
"""Return True if self!=other (same reader and same atr).Returns
False otherwise."""
return not self.__eq__(other)
def __hash__(self):
"""Returns a hash value for this object (str(self) is unique)."""
return hash(str(self))
def createConnection(self):
"""Return a CardConnection to the Card object."""
readerobj = None
if isinstance(self.reader, Reader):
readerobj = self.reader
elif isinstance(self.reader, str):
for reader in readers():
if self.reader == str(reader):
readerobj = reader
if readerobj:
return readerobj.createConnection()
# raise CardConnectionException(
# 'not a valid reader: ' + str(self.reader))
return None
@@ -0,0 +1,252 @@
"""The CardConnection abstract class manages connections with a card and
apdu transmission.
__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.CardConnectionEvent import CardConnectionEvent
from smartcard.Observer import Observable
class CardConnection(Observable):
"""Card connection abstract class."""
T0_protocol = 0x00000001
""" protocol T=0 """
T1_protocol = 0x00000002
""" protocol T=1 """
RAW_protocol = 0x00010000
""" protocol RAW (direct access to the reader) """
T15_protocol = 0x00000008
""" protocol T=15 """
def __init__(self, reader):
"""Construct a new card connection.
@param reader: name of the reader in which the smartcard to connect
to is located.
"""
Observable.__init__(self)
self.reader = reader
""" reader name """
self.errorcheckingchain = None
""" see L{setErrorCheckingChain} """
self.defaultprotocol = CardConnection.T0_protocol | CardConnection.T1_protocol
""" see L{setProtocol} and L{getProtocol} """
def __del__(self):
"""Connect to card."""
def addSWExceptionToFilter(self, exClass):
"""Add a status word exception class to be filtered.
@param exClass: the class to filter, e.g.
L{smartcard.sw.SWExceptions.WarningProcessingException}
Filtered exceptions will not be raised when encountered in the
error checking chain."""
if self.errorcheckingchain is not None:
self.errorcheckingchain[0].addFilterException(exClass)
def addObserver(self, observer):
"""Add a L{CardConnection} observer."""
Observable.addObserver(self, observer)
def deleteObserver(self, observer):
"""Remove a L{CardConnection} observer."""
Observable.deleteObserver(self, observer)
def connect(self, protocol=None, mode=None, disposition=None):
"""Connect to card.
@param protocol: a bit mask of the protocols to use, from
L{CardConnection.T0_protocol}, L{CardConnection.T1_protocol},
L{CardConnection.RAW_protocol}, L{CardConnection.T15_protocol}
@param mode: C{smartcard.scard.SCARD_SHARE_SHARED} (default),
C{smartcard.scard.SCARD_SHARE_EXCLUSIVE} or
C{smartcard.scard.SCARD_SHARE_DIRECT}
@param disposition: C{smartcard.scard.SCARD_LEAVE_CARD}
(default), C{smartcard.scard.SCARD_RESET_CARD},
C{smartcard.scard.SCARD_UNPOWER_CARD} or
C{smartcard.scard.SCARD_EJECT_CARD}
"""
# pylint: disable=unused-argument
Observable.setChanged(self)
Observable.notifyObservers(self, CardConnectionEvent("connect"))
def reconnect(self, protocol=None, mode=None, disposition=None):
"""Reconnect to card.
@param protocol: a bit mask of the protocols to use, from
L{CardConnection.T0_protocol}, L{CardConnection.T1_protocol},
L{CardConnection.RAW_protocol}, L{CardConnection.T15_protocol}
@param mode: C{smartcard.scard.SCARD_SHARE_SHARED} (default),
C{smartcard.scard.SCARD_SHARE_EXCLUSIVE} or
C{smartcard.scard.SCARD_SHARE_DIRECT}
@param disposition: C{smartcard.scard.SCARD_LEAVE_CARD},
C{smartcard.scard.SCARD_RESET_CARD} (default),
C{smartcard.scard.SCARD_UNPOWER_CARD} or
C{smartcard.scard.SCARD_EJECT_CARD}
"""
# pylint: disable=unused-argument
Observable.setChanged(self)
Observable.notifyObservers(self, CardConnectionEvent("reconnect"))
def disconnect(self):
"""Disconnect from card."""
Observable.setChanged(self)
Observable.notifyObservers(self, CardConnectionEvent("disconnect"))
def release(self):
"""Release the context."""
Observable.setChanged(self)
Observable.notifyObservers(self, CardConnectionEvent("release"))
def getATR(self):
"""Return card ATR"""
def getProtocol(self):
"""Return bit mask for the protocol of connection, or None if no
protocol set. The return value is a bit mask of
L{CardConnection.T0_protocol}, L{CardConnection.T1_protocol},
L{CardConnection.RAW_protocol}, L{CardConnection.T15_protocol}
"""
return self.defaultprotocol
def getReader(self):
"""Return card connection reader"""
return self.reader
def setErrorCheckingChain(self, errorcheckingchain):
"""Add an error checking chain.
@param errorcheckingchain: a L{smartcard.sw.ErrorCheckingChain}
object The error checking strategies in errorchecking chain will
be tested with each received response APDU, and a
L{smartcard.sw.SWExceptions.SWException} will be raised upon
error."""
self.errorcheckingchain = errorcheckingchain
def setProtocol(self, protocol):
"""Set protocol for card connection.
@param protocol: a bit mask of L{CardConnection.T0_protocol},
L{CardConnection.T1_protocol}, L{CardConnection.RAW_protocol},
L{CardConnection.T15_protocol}
>>> setProtocol(CardConnection.T1_protocol | CardConnection.T0_protocol)
"""
self.defaultprotocol = protocol
def transmit(self, command, protocol=None):
"""Transmit an apdu. Internally calls L{doTransmit()} class method
and notify observers upon command/response APDU events.
Subclasses must override the L{doTransmit()} class method.
@param command: list of bytes to transmit
@param protocol: the transmission protocol, from
L{CardConnection.T0_protocol},
L{CardConnection.T1_protocol}, or
L{CardConnection.RAW_protocol}
"""
Observable.setChanged(self)
Observable.notifyObservers(
self, CardConnectionEvent("command", [command, protocol])
)
data, sw1, sw2 = self.doTransmit(command, protocol)
Observable.setChanged(self)
Observable.notifyObservers(
self, CardConnectionEvent("response", [data, sw1, sw2])
)
if self.errorcheckingchain is not None:
self.errorcheckingchain[0](data, sw1, sw2)
return data, sw1, sw2
def doTransmit(self, command, protocol):
"""Performs the command APDU transmission.
Subclasses must override this method for implementing apdu
transmission."""
# pylint: disable=unused-argument
return [], 0, 0
def control(self, controlCode, command=None):
"""Send a control command and buffer. Internally calls
L{doControl()} class method and notify observers upon
command/response events. Subclasses must override the
L{doControl()} class method.
@param controlCode: command code
@param command: list of bytes to transmit
"""
if command is None:
command = []
Observable.setChanged(self)
Observable.notifyObservers(
self, CardConnectionEvent("command", [controlCode, command])
)
data = self.doControl(controlCode, command)
Observable.setChanged(self)
Observable.notifyObservers(self, CardConnectionEvent("response", data))
if self.errorcheckingchain is not None:
self.errorcheckingchain[0](data)
return data
def doControl(self, controlCode, command):
"""Performs the command control.
Subclasses must override this method for implementing control."""
# pylint: disable=unused-argument
return []
def getAttrib(self, attribId):
"""return the requested attribute
@param attribId: attribute id like
C{smartcard.scard.SCARD_ATTR_VENDOR_NAME}
"""
Observable.setChanged(self)
Observable.notifyObservers(self, CardConnectionEvent("attrib", [attribId]))
data = self.doGetAttrib(attribId)
if self.errorcheckingchain is not None:
self.errorcheckingchain[0](data)
return data
def doGetAttrib(self, attribId):
"""Performs the command get attrib.
Subclasses must override this method for implementing get attrib."""
# pylint: disable=unused-argument
return []
def __enter__(self):
"""Enter the runtime context."""
return self
def __exit__(self, e_type, value, traceback):
"""Exit the runtime context trying to disconnect."""
self.disconnect()
@@ -0,0 +1,105 @@
"""The CardConnectionDecorator is a Decorator around the CardConnection
abstract class, and allows dynamic addition of features to the
CardConnection, e.g. implementing a secure channel..
__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
"""
class CardConnectionDecorator:
"""Card connection decorator class."""
def __init__(self, cardConnectionComponent):
"""Construct a new card connection decorator.
CardConnectionComponent: CardConnection component to decorate
"""
self.component = cardConnectionComponent
def addSWExceptionToFilter(self, exClass):
"""call inner component addSWExceptionToFilter"""
self.component.addSWExceptionToFilter(exClass)
def addObserver(self, observer):
"""call inner component addObserver"""
self.component.addObserver(observer)
def deleteObserver(self, observer):
"""call inner component deleteObserver"""
self.component.deleteObserver(observer)
def connect(self, protocol=None, mode=None, disposition=None):
"""call inner component connect"""
self.component.connect(protocol, mode, disposition)
def reconnect(self, protocol=None, mode=None, disposition=None):
"""call inner component reconnect"""
self.component.reconnect(protocol, mode, disposition)
def disconnect(self):
"""call inner component disconnect"""
self.component.disconnect()
def release(self):
"""call inner component release"""
self.component.release()
def getATR(self):
"""call inner component getATR"""
return self.component.getATR()
def getProtocol(self):
"""call inner component getProtocol"""
return self.component.getProtocol()
def getReader(self):
"""call inner component getReader"""
return self.component.getReader()
def setErrorCheckingChain(self, errorcheckingchain):
"""call inner component setErrorCheckingChain"""
self.component.setErrorCheckingChain(errorcheckingchain)
def setProtocol(self, protocol):
"""call inner component setProtocol"""
return self.component.setProtocol(protocol)
def transmit(self, command, protocol=None):
"""call inner component transmit"""
return self.component.transmit(command, protocol)
def control(self, controlCode, command=None):
"""call inner component control"""
if command is None:
command = []
return self.component.control(controlCode, command)
def getAttrib(self, attribId):
"""call inner component getAttrib"""
return self.component.getAttrib(attribId)
def __enter__(self):
return self
def __exit__(self, e_type, value, traceback):
self.disconnect()
self.release()
@@ -0,0 +1,42 @@
"""The CardConnectionEvent is sent to CardConnectionObserver objects
when a CardConnection event occurs.
__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
"""
# pylint: disable=too-few-public-methods
class CardConnectionEvent:
"""Base class for card connection events.
This event is notified by CardConnection objects."""
def __init__(self, event_type, args=None):
"""
@param event_type: 'connect', 'reconnect', 'disconnect', 'command', 'response'
@param args: None for 'connect', 'reconnect' or 'disconnect'
command APDU byte list for 'command'
[response data, sw1, sw2] for 'response'
"""
self.type = event_type
self.args = args
@@ -0,0 +1,77 @@
"""CardConnectionObserver interface.
CardConnectionObserver is a base class for objects that are to be notified
upon CardConnection events.
__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.Observer import Observer
from smartcard.util import toHexString
# pylint: disable=too-few-public-methods
# ReaderObserver interface
class CardConnectionObserver(Observer):
"""
CardConnectionObserver is a base class for objects that are to be notified
upon L{CardConnection} events.
"""
def update(self, observable, handlers):
"""Called upon CardConnection event.
@param observable: the observed card connection object
@param handlers: the CardConnectionEvent sent by the connection
"""
class ConsoleCardConnectionObserver(CardConnectionObserver):
"""CardConnectionObserver output to the console"""
def update(self, observable, handlers):
if "connect" == handlers.type:
print("connecting to " + observable.getReader())
elif "reconnect" == handlers.type:
print("reconnecting to " + observable.getReader())
elif "disconnect" == handlers.type:
print("disconnecting from " + observable.getReader())
elif "release" == handlers.type:
print("release from " + observable.getReader())
elif "command" == handlers.type:
print("> " + toHexString(handlers.args[0]))
elif "response" == handlers.type:
sw1, sw2 = handlers.args[-2:]
SW = f" {sw1:2X} {sw2:02X}"
if [] == handlers.args[0]:
print("< []" + SW)
else:
print("< " + toHexString(handlers.args[0]) + SW)
else:
print("unknown event:", handlers.type)
@@ -0,0 +1,257 @@
"""Smart card insertion/removal monitoring classes.
CardObserver is a base class for objects that are to be notified
upon smart card insertion/removal.
CardMonitor is a singleton object notifying registered CardObservers
upon reader insertion/removal.
__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 traceback
from threading import Event, Lock, Thread
from time import sleep
from smartcard.CardRequest import CardRequest
from smartcard.Exceptions import CardRequestTimeoutException, SmartcardException
from smartcard.Observer import Observable, Observer
from smartcard.scard import SCARD_E_NO_SERVICE
_START_ON_DEMAND_ = False
# pylint: disable=too-few-public-methods
# CardObserver interface
class CardObserver(Observer):
"""
CardObserver is a base abstract class for objects that are to be notified
upon smart card insertion / removal.
"""
def __init__(self):
pass
def update(self, observable, handlers):
"""Called upon smart card insertion / removal.
@param observable:
@param handlers:
- addedcards: list of inserted smart cards causing notification
- removedcards: list of removed smart cards causing notification
"""
class CardMonitor:
"""Class that monitors smart card insertion / removals.
and notify observers
note: a card monitoring thread will be running
as long as the card monitor has observers, or CardMonitor.stop()
is called. Do not forget to delete all your observers by
calling L{deleteObserver}, or your program will run forever...
Uses the singleton pattern from Thinking in Python
Bruce Eckel, http://mindview.net/Books/TIPython to make sure
there is only one L{CardMonitor}.
"""
class __CardMonitorSingleton(Observable):
"""The real smart card monitor class.
A single instance of this class is created
by the public CardMonitor class.
"""
def __init__(self):
Observable.__init__(self)
if _START_ON_DEMAND_:
self.rmthread = None
else:
self.rmthread = CardMonitoringThread(self)
def addObserver(self, observer):
"""Add an observer.
We only start the card monitoring thread when
there are observers.
"""
Observable.addObserver(self, observer)
if _START_ON_DEMAND_:
if self.countObservers() > 0 and self.rmthread is None:
self.rmthread = CardMonitoringThread(self)
else:
observer.update(self, (self.rmthread.cards, []))
def deleteObserver(self, observer):
"""Remove an observer.
We delete the L{CardMonitoringThread} reference when there
are no more observers.
"""
Observable.deleteObserver(self, observer)
if _START_ON_DEMAND_:
if self.countObservers() == 0:
if self.rmthread is not None:
self.rmthread.stop()
self.rmthread.join()
self.rmthread = None
def __str__(self):
return "CardMonitor"
# the singleton
instance = None
lock = Lock()
def __init__(self):
with CardMonitor.lock:
if not CardMonitor.instance:
CardMonitor.instance = CardMonitor.__CardMonitorSingleton()
def __getattr__(self, name):
return getattr(self.instance, name)
class CardMonitoringThread:
"""Card insertion thread.
This thread waits for card insertion.
"""
class __CardMonitoringThreadSingleton(Thread):
"""The real card monitoring thread class.
A single instance of this class is created
by the public L{CardMonitoringThread} class.
"""
def __init__(self, observable):
Thread.__init__(self)
self.observable = observable
self.stopEvent = Event()
self.stopEvent.clear()
self.cards = []
self.daemon = True
self.cardrequest = None
# the actual monitoring thread
def run(self):
"""Runs until stopEvent is notified, and notify
observers of all card insertion/removal.
"""
self.cardrequest = CardRequest(timeout=60)
while not self.stopEvent.is_set():
try:
currentcards = self.cardrequest.waitforcardevent()
addedcards = []
for card in currentcards:
if card not in self.cards:
addedcards.append(card)
removedcards = []
for card in self.cards:
if card not in currentcards:
removedcards.append(card)
if addedcards or removedcards:
self.cards = currentcards
self.observable.setChanged()
self.observable.notifyObservers((addedcards, removedcards))
except CardRequestTimeoutException:
pass
except SmartcardException as exc:
traceback.print_exc()
# Most likely raised during interpreter shutdown due
# to unclean exit which failed to remove all observers.
# To solve this, we set the stop event and pass the
# exception to let the thread finish gracefully.
if exc.hresult == SCARD_E_NO_SERVICE:
self.stopEvent.set()
def stop(self):
"""stop the thread by signaling stopEvent"""
self.stopEvent.set()
# the singleton
instance = None
lock = Lock()
def __init__(self, observable):
with CardMonitoringThread.lock:
if not CardMonitoringThread.instance:
CardMonitoringThread.instance = (
CardMonitoringThread.__CardMonitoringThreadSingleton(observable)
)
CardMonitoringThread.instance.start()
def join(self, *args, **kwargs):
"""wait for the CardMonitoringThread thread"""
with CardMonitoringThread.lock:
if self.instance:
self.instance.join(*args, **kwargs)
CardMonitoringThread.instance = None
def __getattr__(self, name):
if self.instance:
return getattr(self.instance, name)
raise SmartcardException(".instance is not set")
if __name__ == "__main__":
print("insert or remove cards in the next 10 seconds")
class printobserver(CardObserver):
"""a simple card observer that prints added/removed cards"""
def __init__(self, obsindex):
self.obsindex = obsindex
def update(self, observable, handlers):
addedcards, removedcards = handlers
print(f"{self.obsindex} - added: {str(addedcards)}")
print(f"{self.obsindex} - removed: {str(removedcards)}")
class testthread(Thread):
"""Test class"""
def __init__(self, obsindex):
Thread.__init__(self)
self.readermonitor = CardMonitor()
self.obsindex = obsindex
self.observer = None
def run(self):
# create and register observer
self.observer = printobserver(self.obsindex)
self.readermonitor.addObserver(self.observer)
sleep(10)
self.readermonitor.deleteObserver(self.observer)
t1 = testthread(1)
t2 = testthread(2)
t1.start()
t2.start()
@@ -0,0 +1,102 @@
"""Smartcard CardRequest.
__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.pcsc.PCSCCardRequest import PCSCCardRequest
class CardRequest:
"""A CardRequest is used for waitForCard() invocations and specifies what
kind of smart card an application is waited for.
"""
# 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 CardRequest.
@param newcardonly: if True, request a new card
default is 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{smartcard.CardType.CardType} to wait for;
default is L{smartcard.CardType.AnyCardType},
i.e. the request will succeed with any card
@param cardServiceClass: the specific card service class to create
and bind to the card default is to create
and bind a L{smartcard.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 None
"""
self.pcsccardrequest = PCSCCardRequest(
newcardonly, readers, cardType, cardServiceClass, timeout
)
def getReaders(self):
"""Returns the list or readers on which to wait for cards."""
return self.pcsccardrequest.getReaders()
def waitforcard(self):
"""Wait for card insertion and returns a card service."""
return self.pcsccardrequest.waitforcard()
def waitforcardevent(self):
"""Wait for card insertion or removal."""
return self.pcsccardrequest.waitforcardevent()
def __enter__(self):
"""Enter the runtime context."""
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
"""Exit the runtime context and release the PC/SC context."""
self.pcsccardrequest.release()
return False
if __name__ == "__main__":
# Small sample illustrating the use of CardRequest.py.
from smartcard.util import toHexString
print("Insert a new card within 10 seconds")
with CardRequest(timeout=10, newcardonly=True) as cr:
with cr.waitforcard() as cs:
cs.connection.connect()
print(cs.connection.getReader() + " " + toHexString(cs.connection.getATR()))
cs.connection.disconnect()
@@ -0,0 +1,76 @@
"""Card service abstract class.
A card service is a class providings specific smart card functionality,
e.g. a GSM file system or an Open Platform loader. CardService is an
abstract class from which concrete card services are derived. A concrete
card service is almost always smart card operating system specific.
The card service performs its specific smart card functionality by accessing
the smartcard with a CardConnection.
__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
"""
class CardService:
"""Card service abstract class."""
def __init__(self, connection, cardname=None):
"""Construct a new card service and bind to a smart card in a reader.
@param connection: the CardConnection used to access the smart card
"""
self.connection = connection
self.cardname = cardname
def __del__(self):
"""Destructor. Disconnect card and destroy card service resources."""
self.__exit__(None, None, None)
def __enter__(self):
"""Enter the runtime context."""
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
"""Exit the runtime context and disconnect + release the PC/SC context."""
self.connection.disconnect()
self.connection.release()
return False
@staticmethod
def supports(cardname):
"""Returns True if the cardname is supported by the card service."""
if __name__ == "__main__":
# Small sample illustrating the use of CardService.
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
from smartcard.System import readers
cc = readers()[0].createConnection()
cs = CardService(cc)
cs.connection.connect()
data, sw1, sw2 = cs.connection.transmit(SELECT + DF_TELECOM)
print(f"{sw1:X} {sw2:X}")
cs.connection.disconnect()
cs.connection.release()
@@ -0,0 +1,108 @@
"""Abstract CarType.
__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.Exceptions import InvalidATRMaskLengthException
from smartcard.System import readers
from smartcard.util import toHexString
# pylint: disable=too-few-public-methods
class CardType:
"""Abstract base class for CardTypes.
Known subclasses: L{smartcard.CardType.AnyCardType}
L{smartcard.CardType.ATRCardType}."""
def __init__(self):
"""CardType constructor."""
def matches(self, atr, reader=None):
"""Returns true if atr and card connected match the L{CardType}.
@param atr: the atr to check for matching
@param reader: the reader (optional); default is None
The reader can be used in some subclasses to do advanced
matching that require connecting to the card."""
class AnyCardType(CardType):
"""The AnyCardType matches any card."""
def matches(self, atr, reader=None):
"""Always returns true, i.e. AnyCardType matches any card.
@param atr: the atr to check for matching
@param reader: the reader (optional); default is None"""
return True
class ATRCardType(CardType):
"""The ATRCardType defines a card from an ATR and a mask."""
def __init__(self, atr, mask=None):
"""ATRCardType constructor.
@param atr: the ATR of the CardType
@param mask: an optional mask to be applied to the ATR for
L{CardType} matching default is None
"""
super().__init__()
self.atr = list(atr)
self.mask = mask
if mask is None:
self.maskedatr = self.atr
else:
if len(self.atr) != len(self.mask):
raise InvalidATRMaskLengthException(toHexString(mask))
self.maskedatr = list(map(lambda x, y: x & y, self.atr, self.mask))
def matches(self, atr, reader=None):
"""Returns true if the atr matches the masked CardType atr.
@param atr: the atr to check for matching
@param reader: the reader (optional); default is None
When atr is compared to the CardType ATR, matches returns true if
and only if CardType.atr & CardType.mask = atr & CardType.mask,
where & is the bitwise logical AND."""
if len(atr) != len(self.atr):
return not True
if self.mask is not None:
maskedatr = list(map(lambda x, y: x & y, list(atr), self.mask))
else:
maskedatr = atr
return self.maskedatr == maskedatr
if __name__ == "__main__":
# Small sample illustrating the use of CardType.py.
r = readers()
print(r)
connection = r[0].createConnection()
connection.connect()
atrct = ATRCardType([0x3B, 0x16, 0x94, 0x20, 0x02, 0x01, 0x00, 0x00, 0x0D])
print(atrct.matches(connection.getATR()))
@@ -0,0 +1,126 @@
"""Smartcard module exceptions.
This module defines the exceptions raised by the smartcard module.
__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.scard import SCardGetErrorMessage
class SmartcardException(Exception):
"""Base class for smartcard exceptions.
smartcard exceptions are generated by the smartcard module and
shield scard (i.e. PCSC) exceptions raised by the scard module.
"""
def __init__(self, *args, message="", hresult=-1):
if not message and len(args) > 0:
message = args[0]
args = args[1:]
if -1 == hresult and len(args) > 0:
hresult = args[0]
args = args[1:]
super().__init__(message, *args)
self.hresult = int(hresult)
def __str__(self):
text = super().__str__()
if self.hresult != -1:
if text:
text += ": "
hresult = self.hresult
if hresult < 0:
# convert 0x-7FEFFFE3 into 0x8010001D
hresult += 0x100000000
text += f"{SCardGetErrorMessage(self.hresult)} (0x{hresult:08X})"
return text
class CardConnectionException(SmartcardException):
"""Raised when a CardConnection class method fails."""
class CardRequestException(SmartcardException):
"""Raised when a CardRequest wait fails."""
class CardRequestTimeoutException(SmartcardException):
"""Raised when a CardRequest times out."""
def __init__(self, *args, hresult=-1):
if -1 == hresult and len(args) > 0:
hresult = args[0]
args = args[1:]
SmartcardException.__init__(
self, "Time-out during card request", hresult=hresult, *args
)
class CardServiceException(SmartcardException):
"""Raised when a CardService class method fails."""
class CardServiceStoppedException(SmartcardException):
"""Raised when the CardService was stopped"""
class CardServiceNotFoundException(SmartcardException):
"""Raised when the CardService is not found"""
class InvalidATRMaskLengthException(SmartcardException):
"""Raised when an ATR mask does not match an ATR length."""
def __init__(self, mask):
SmartcardException.__init__(self, f"Invalid ATR mask length: {mask}")
class InvalidReaderException(SmartcardException):
"""Raised when trying to access an invalid smartcard reader."""
def __init__(self, readername):
SmartcardException.__init__(self, f"Invalid reader: {readername}")
class ListReadersException(SmartcardException):
"""Raised when smartcard readers cannot be listed."""
def __init__(self, hresult):
SmartcardException.__init__(self, "Failed to list readers", hresult=hresult)
class NoCardException(SmartcardException):
"""Raised when no card in is present in reader."""
def __init__(self, message, hresult):
SmartcardException.__init__(self, message, hresult=hresult)
class NoReadersException(SmartcardException):
"""Raised when the system has no smartcard reader."""
def __init__(self, *args):
SmartcardException.__init__(self, "No reader found", *args)
@@ -0,0 +1,80 @@
"""CardConnectionDecorator that provides exclusive use of a card.
__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 smartcard.pcsc
from smartcard.CardConnectionDecorator import CardConnectionDecorator
from smartcard.Exceptions import CardConnectionException
from smartcard.pcsc import PCSCCardConnection
from smartcard.scard import (
SCARD_LEAVE_CARD,
SCARD_S_SUCCESS,
SCARD_SHARE_EXCLUSIVE,
SCardConnect,
SCardDisconnect,
SCardGetErrorMessage,
)
class ExclusiveConnectCardConnection(CardConnectionDecorator):
"""This decorator uses exclusive access to the card during
connection to prevent other processes to connect to this card."""
def __init__(self, cardconnection):
CardConnectionDecorator.__init__(self, cardconnection)
def connect(self, protocol=None, mode=None, disposition=None):
"""Disconnect and reconnect in exclusive mode PCSCCardconnections."""
CardConnectionDecorator.connect(self, protocol, mode, disposition)
component = self.component
while True:
if isinstance(
component, smartcard.pcsc.PCSCCardConnection.PCSCCardConnection
):
pcscprotocol = PCSCCardConnection.translateprotocolmask(protocol)
if 0 == pcscprotocol:
pcscprotocol = component.getProtocol()
if component.hcard is not None:
hresult = SCardDisconnect(component.hcard, SCARD_LEAVE_CARD)
if hresult != SCARD_S_SUCCESS:
raise CardConnectionException(
"Failed to disconnect: " + SCardGetErrorMessage(hresult)
)
hresult, component.hcard, _ = SCardConnect(
component.hcontext,
str(component.reader),
SCARD_SHARE_EXCLUSIVE,
pcscprotocol,
)
if hresult != SCARD_S_SUCCESS:
raise CardConnectionException(
"Failed to connect with SCARD_SHARE_EXCLUSIVE"
+ SCardGetErrorMessage(hresult)
)
# print('reconnected exclusive')
break
if hasattr(component, "component"):
component = component.component
else:
break
@@ -0,0 +1,93 @@
"""Sample CardConnectionDecorator that provides exclusive transmit()
__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 smartcard.pcsc
from smartcard.CardConnectionDecorator import CardConnectionDecorator
from smartcard.Exceptions import CardConnectionException
from smartcard.scard import (
SCARD_LEAVE_CARD,
SCARD_S_SUCCESS,
SCardBeginTransaction,
SCardEndTransaction,
SCardGetErrorMessage,
)
class ExclusiveTransmitCardConnection(CardConnectionDecorator):
"""This decorator uses
L{SCardBeginTransaction}/L{SCardEndTransaction} to preserve other
processes of threads to access the card during transmit()."""
def __init__(self, cardconnection):
CardConnectionDecorator.__init__(self, cardconnection)
def lock(self):
"""Lock card with L{SCardBeginTransaction}."""
# pylint: disable=duplicate-code
component = self.component
while True:
if isinstance(
component, smartcard.pcsc.PCSCCardConnection.PCSCCardConnection
):
hresult = SCardBeginTransaction(component.hcard)
if SCARD_S_SUCCESS != hresult:
raise CardConnectionException(
"Failed to lock with SCardBeginTransaction: "
+ SCardGetErrorMessage(hresult)
)
break
if hasattr(component, "component"):
component = component.component
else:
break
def unlock(self):
"""Unlock card with L{SCardEndTransaction}."""
# pylint: disable=duplicate-code
component = self.component
while True:
if isinstance(
component, smartcard.pcsc.PCSCCardConnection.PCSCCardConnection
):
hresult = SCardEndTransaction(component.hcard, SCARD_LEAVE_CARD)
if SCARD_S_SUCCESS != hresult:
raise CardConnectionException(
"Failed to unlock with SCardEndTransaction: "
+ SCardGetErrorMessage(hresult)
)
break
if hasattr(component, "component"):
component = component.component
else:
break
def transmit(self, command, protocol=None):
"""Gain exclusive access to card during APDU transmission for if this
decorator decorates a PCSCCardConnection."""
data, sw1, sw2 = CardConnectionDecorator.transmit(self, command, protocol)
return data, sw1, sw2
@@ -0,0 +1,93 @@
"""
from Thinking in Python, Bruce Eckel
https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Observer.html
(c) Copyright 2008, Creative Commons Attribution-Share Alike 3.0.
Class support for "observer" pattern.
The observer class is the base class
for all smartcard package observers.
"""
from __future__ import annotations
import typing
from smartcard.Synchronization import Synchronization, synchronize
# pylint: disable=too-few-public-methods
class Observer:
"""Observer"""
def update(self, observable: Observable, handlers: typing.Any) -> None:
"""Called when the observed object is
modified. You call an Observable object's
notifyObservers method to notify all the
object's observers of the change."""
class Observable(Synchronization):
"""Observable"""
def __init__(self) -> None:
super().__init__()
self.obs: list[Observer] = []
self.changed = 0
def addObserver(self, observer: Observer) -> None:
"""Add an observer"""
if observer not in self.obs:
self.obs.append(observer)
def deleteObserver(self, observer: Observer) -> None:
"""Remove an observer"""
self.obs.remove(observer)
def notifyObservers(self, handlers: typing.Any = None) -> None:
"""If 'changed' indicates that this object
has changed, notify all its observers, then
call clearChanged(). Each observer has its
update() called with two arguments: this
observable object and the generic 'handlers'."""
with self.mutex:
if not self.changed:
return
# Make a copy of the observer list.
observers = self.obs.copy()
self.changed = 0
# Update observers
for observer in observers:
observer.update(self, handlers)
def deleteObservers(self) -> None:
"""Remove all observers"""
self.obs = []
def setChanged(self) -> None:
"""Set the change flag"""
self.changed = 1
def clearChanged(self) -> None:
"""Clear the change flag"""
self.changed = 0
def hasChanged(self) -> int:
"""Somethig has changed?"""
return self.changed
def countObservers(self) -> int:
"""Return the number of Observers"""
return len(self.obs)
synchronize(
Observable,
"addObserver deleteObserver deleteObservers "
+ "setChanged clearChanged hasChanged "
+ "countObservers",
)
@@ -0,0 +1,70 @@
"""Card service abstract class.
A card service is a class providings specific smart card functionality,
e.g. a GSM file system or an Open Platform loader. CardService is an
abstract class from which concrete card services are derived. A concrete
card service is almost always smart card operating system specific
__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 import CardService
class PassThruCardService(CardService.CardService):
"""Pass-thru card service class."""
def __init__(self, connection, cardname=None):
"""Construct a pass-thru card service.
@param connection: the CardConnection used to access the smart card
"""
CardService.CardService.__init__(self, connection, cardname)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, exc_traceback):
return CardService.CardService.__exit__(
self, exc_type, exc_value, exc_traceback
)
@staticmethod
def supports(cardname):
"""Returns True if the cardname is supported by the card service.
The PassThruCardService supports all cardnames and always
returns True."""
return True
if __name__ == "__main__":
# Small sample illustrating the use of CardService.
SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
DF_TELECOM = [0x7F, 0x10]
from smartcard.System import readers
cc = readers()[0].createConnection()
cs = PassThruCardService(cc)
cs.connection.connect()
data, sw1, sw2 = cs.connection.transmit(SELECT + DF_TELECOM)
print(f"{sw1:X} {sw2:X}")
cs.connection.disconnect()
@@ -0,0 +1,248 @@
"""Smart card reader monitoring classes.
ReaderObserver is a base class for objects that are to be notified
upon smartcard reader insertion/removal.
ReaderMonitor is a singleton object notifying registered ReaderObservers
upon reader insertion/removal.
__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 _thread
import traceback
from threading import Event, Thread
from time import sleep
import smartcard.System
from smartcard.Exceptions import SmartcardException
from smartcard.Observer import Observable, Observer
from smartcard.Synchronization import synchronize
# pylint: disable=too-few-public-methods
# ReaderObserver interface
class ReaderObserver(Observer):
"""
ReaderObserver is a base abstract class for objects that are to be notified
upon smartcard reader insertion/removal.
"""
def __init__(self):
pass
def update(self, observable, handlers):
"""Called upon reader insertion/removal.
@param observable:
@param handlers:
- addedreaders: list of added readers causing notification
- removedreaders: list of removed readers causing notification
"""
class ReaderMonitor(Observable):
"""Class that monitors reader insertion/removal.
and notify observers
note: a reader monitoring thread will be running
as long as the reader monitor has observers, or ReaderMonitor.stop()
is called.
It implements the shared state design pattern, where objects
of the same type all share the same state, in our case essentially
the ReaderMonitoring Thread. Thanks to Frank Aune for implementing
the shared state pattern logics.
"""
__shared_state = {}
def __init__(
self, startOnDemand=True, readerProc=smartcard.System.readers, period=1
):
self.__dict__ = self.__shared_state
Observable.__init__(self)
self.startOnDemand = startOnDemand
self.readerProc = readerProc
self.period = period
if self.startOnDemand:
self.rmthread = None
else:
self.rmthread = ReaderMonitoringThread(self, self.readerProc, self.period)
self.rmthread.start()
def addObserver(self, observer):
"""Add an observer."""
Observable.addObserver(self, observer)
# If self.startOnDemand is True, the reader monitoring
# thread only runs when there are observers.
if self.startOnDemand:
if 0 < self.countObservers():
if not self.rmthread:
self.rmthread = ReaderMonitoringThread(
self, self.readerProc, self.period
)
# start reader monitoring thread in another thread to
# avoid a deadlock; addObserver and notifyObservers called
# in the ReaderMonitoringThread run() method are
# synchronized
_thread.start_new_thread(self.rmthread.start, ())
else:
observer.update(self, (self.rmthread.readers, []))
def deleteObserver(self, observer):
"""Remove an observer."""
Observable.deleteObserver(self, observer)
# If self.startOnDemand is True, the reader monitoring
# thread is stopped when there are no more observers.
if self.startOnDemand:
if 0 == self.countObservers():
self.rmthread.stop()
del self.rmthread
self.rmthread = None
def __str__(self):
return self.__class__.__name__
synchronize(
ReaderMonitor,
"addObserver deleteObserver deleteObservers "
+ "setChanged clearChanged hasChanged "
+ "countObservers",
)
class ReaderMonitoringThread(Thread):
"""Reader insertion thread.
This thread polls for pcsc reader insertion, since no
reader insertion event is available in pcsc.
"""
# pylint: disable=too-many-instance-attributes
__shared_state = {}
def __init__(self, observable, readerProc, period):
self.__dict__ = self.__shared_state
Thread.__init__(self)
self.observable = observable
self.stopEvent = Event()
self.stopEvent.clear()
self.readers = []
self.daemon = True
self.name = "smartcard.ReaderMonitoringThread"
self.readerProc = readerProc
self.period = period
def run(self):
"""Runs until stopEvent is notified, and notify
observers of all reader insertion/removal.
"""
# pylint: disable=too-many-nested-blocks
while not self.stopEvent.is_set():
try:
# no need to monitor if no observers
if 0 < self.observable.countObservers():
currentReaders = self.readerProc()
addedReaders = []
removedReaders = []
if currentReaders != self.readers:
for reader in currentReaders:
if reader not in self.readers:
addedReaders.append(reader)
for reader in self.readers:
if reader not in currentReaders:
removedReaders.append(reader)
if addedReaders or removedReaders:
# Notify observers
self.readers = []
for r in currentReaders:
self.readers.append(r)
self.observable.setChanged()
self.observable.notifyObservers(
(addedReaders, removedReaders)
)
# wait every second on stopEvent
self.stopEvent.wait(self.period)
except SmartcardException:
traceback.print_exc()
# Most likely raised during interpreter shutdown due
# to unclean exit which failed to remove all observers.
# To solve this, we set the stop event and pass the
# exception to let the thread finish gracefully.
self.stopEvent.set()
def stop(self):
"""stop the thread by signaling stopEvent"""
self.stopEvent.set()
self.join()
if __name__ == "__main__":
print("insert or remove readers in the next 20 seconds")
class printobserver(ReaderObserver):
"""a simple reader observer that prints added/removed readers"""
def __init__(self, obsindex):
self.obsindex = obsindex
def update(self, observable, handlers):
addedreaders, removedreaders = handlers
print(f"{self.obsindex} - added: {addedreaders}")
print(f"{self.obsindex} - removed: {removedreaders}")
class testthread(Thread):
"""Test class"""
# pylint: disable=duplicate-code
def __init__(self, obsindex):
Thread.__init__(self)
self.readermonitor = ReaderMonitor()
self.obsindex = obsindex
self.observer = None
def run(self):
# create and register observer
self.observer = printobserver(self.obsindex)
self.readermonitor.addObserver(self.observer)
sleep(20)
self.readermonitor.deleteObserver(self.observer)
t1 = testthread(1)
t2 = testthread(2)
t1.start()
t2.start()
t1.join()
t2.join()
@@ -0,0 +1,114 @@
"""
Smartcard Session.
__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.Exceptions import InvalidReaderException, NoReadersException
from smartcard.PassThruCardService import PassThruCardService
from smartcard.System import readers
class Session:
"""The Session object enables programmers to transmit APDU to smartcards.
This is an example of use of the Session object:
>>> import smartcard
>>> reader=smartcard.listReaders()
>>> s = smartcard.Session(reader[0])
>>> SELECT = [0xA0, 0xA4, 0x00, 0x00, 0x02]
>>> DF_TELECOM = [0x7F, 0x10]
>>> data, sw1, sw2 = s.sendCommandAPDU(SELECT+DF_TELECOM)
>>> print(data, sw1, sw2)
>>> s.close()
>>> print(`s`)
"""
def __init__(self, readerName=None):
"""Session constructor. Initializes a smart card session and
connect to the card.
@param readerName: reader to connect to; default is first PCSC reader
"""
# if reader name not given, select first reader
if readerName is None:
if len(readers()) > 0:
self.reader = readers()[0]
self.readerName = repr(self.reader)
else:
raise NoReadersException()
# otherwise select reader from name
else:
self.readerName = readerName
for reader in readers():
if readerName == str(reader):
self.reader = reader
self.readerName = repr(self.reader)
try:
self.reader
except AttributeError as exc:
raise InvalidReaderException(self.readerName) from exc
# open card connection and bind PassThruCardService
cc = self.reader.createConnection()
self.cs = PassThruCardService(cc)
self.cs.connection.connect()
def close(self):
"""Close the smartcard session.
Closing a session will disconnect from the card."""
self.cs.connection.disconnect()
def sendCommandAPDU(self, command):
"""Send an APDU command to the connected smartcard.
@param command: list of APDU bytes, e.g. [0xA0, 0xA4, 0x00, 0x00, 0x02]
@return: a tuple (response, sw1, sw2) where
response is the APDU response
sw1, sw2 are the two status words
"""
response, sw1, sw2 = self.cs.connection.transmit(command)
if len(response) > 2:
response.append(sw1)
response.append(sw2)
return response, sw1, sw2
def getATR(self):
"""Returns the ATR of the connected card."""
return self.cs.connection.getATR()
def __repr__(self):
"""Returns a string representation of the session."""
return f"<Session instance: readerName={self.readerName}>"
if __name__ == "__main__":
# Small sample illustrating the use of Session.py.
pass
@@ -0,0 +1,62 @@
"""
from Thinking in Python, Bruce Eckel
https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Observer.html
(c) Copyright 2008, Creative Commons Attribution-Share Alike 3.0.
Simple emulation of Java's 'synchronized'
keyword, from Peter Norvig.
"""
from __future__ import annotations
import functools
import sys
import threading
from collections.abc import Iterable
from typing import Any, Callable, Protocol, TypeVar
# pylint: disable=too-few-public-methods
if sys.version_info >= (3, 10):
from typing import ParamSpec
else:
from typing_extensions import ParamSpec
T = TypeVar("T")
P = ParamSpec("P")
def synchronized(method: Callable[P, T]) -> Callable[P, T]:
"""Synchronize methods with the same mutex"""
@functools.wraps(method)
def f(self: _SynchronizationProtocol, *args: Any, **kwargs: Any) -> Any:
with self.mutex:
return method(self, *args, **kwargs)
return f
def synchronize(klass: type, names: str | Iterable[str] | None = None) -> None:
"""Synchronize methods in the given class.
Only synchronize the methods whose names are
given, or all methods if names=None."""
if isinstance(names, str):
names = names.split()
for name, val in list(klass.__dict__.items()):
if callable(val) and name != "__init__" and (names is None or name in names):
setattr(klass, name, synchronized(val))
class _SynchronizationProtocol(Protocol):
mutex: threading.Lock | threading.RLock
class Synchronization(_SynchronizationProtocol):
"""You can create your own self.mutex, or inherit from this class"""
def __init__(self):
self.mutex = threading.RLock()
@@ -0,0 +1,68 @@
"""Smartcard system utility functions and classes.
Manages smartcard readers and reader 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
"""
import smartcard.pcsc.PCSCReaderGroups
import smartcard.reader.ReaderFactory
def readers(groups=None):
"""Returns the list of smartcard readers in groups as
L{smartcard.reader.Reader}.
If groups is not specified, returns the list of all smartcard readers.
>>> import smartcard
>>> r=smartcard.readers()
>>> r=smartcard.readers(['SCard$DefaultReaders', 'MyReaderGroup'])
"""
if groups is None:
groups = []
return smartcard.reader.ReaderFactory.ReaderFactory.readers(groups)
def readergroups():
"""Returns the list of reader groups."""
return smartcard.pcsc.PCSCReaderGroups.PCSCReaderGroups().instance
# for legacy only
def listReaders():
"""Returns the list of smartcard readers.
Deprecated - Use L{smartcard.System.readers()} instead.
"""
zreaders = []
for reader in readers():
zreaders.append(str(reader))
return zreaders
if __name__ == "__main__":
print(readers())
print(readers(["SCard$DefaultReaders"]))
print(readergroups())
@@ -0,0 +1,33 @@
"""Smartcard utility module.
The smartcard utility module provides classes and functions to
access smartcards and 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.Session import Session
# for legacy only
from smartcard.System import listReaders
__all__ = ["listReaders", "Session"]
@@ -0,0 +1,46 @@
"""smartcard.guid
Utility functions to handle GUIDs as strings or list of bytes
__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 uuid
def strToGUID(s: str) -> list[int]:
"""Converts a GUID string into a list of bytes.
>>> strToGUID('{AD4F1667-EA75-4124-84D4-641B3B197C65}')
[103, 22, 79, 173, 117, 234, 36, 65, 132, 212, 100, 27, 59, 25, 124, 101]
"""
return list(uuid.UUID(s).bytes_le)
def GUIDToStr(g: list[int]) -> str:
"""Converts a GUID sequence of bytes into a string.
>>> GUIDToStr([103,22,79,173, 117,234, 36,65,
... 132, 212, 100, 27, 59, 25, 124, 101])
'{AD4F1667-EA75-4124-84D4-641B3B197C65}'
"""
return f"{{{uuid.UUID(bytes_le=bytes(g))}}}".upper()
@@ -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())
@@ -0,0 +1,63 @@
"""Smart card Reader abstract class.
__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
"""
class Reader:
"""Reader abstract class.
The reader class is responsible for creating connections
with a card.
"""
def __init__(self, readername):
"""Constructs a new reader and store readername."""
self.name = readername
def addtoreadergroup(self, groupname):
"""Add reader to a reader group."""
def removefromreadergroup(self, groupname):
"""Remove reader from a reader group."""
def createConnection(self):
"""Returns a card connection thru reader."""
def __eq__(self, other):
"""Returns True if self==other (same name)."""
if isinstance(other, type(self)):
return self.name == other.name
return False
def __hash__(self):
"""Returns a hash value for this object (self.name is unique)."""
return hash(self.name)
def __repr__(self):
"""Returns card reader name string for `object` calls."""
return f"'{self.name}'"
def __str__(self):
"""Returns card reader name string for str(object) calls."""
return self.name
@@ -0,0 +1,65 @@
"""ReaderFactory: creates smartcard readers.
__author__ = "gemalto https://www.gemalto.com/"
Factory pattern implementation borrowed from
Thinking in Python, Bruce Eckel,
http://mindview.net/Books/TIPython
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 importlib
from smartcard.pcsc.PCSCReader import PCSCReader
class ReaderFactory:
"""Class to create readers from reader type id."""
factories = {}
factorymethods = [PCSCReader.readers]
# A Template Method:
@staticmethod
def createReader(clazz: str, readername: str):
"""Static method to create a reader from a reader clazz.
@param clazz: the reader class name
@param readername: the reader name
"""
if clazz not in ReaderFactory.factories:
module_name, _, class_name = clazz.rpartition(".")
imported_module = importlib.import_module(module_name)
imported_class = getattr(imported_module, class_name)
ReaderFactory.factories[clazz] = imported_class.Factory()
return ReaderFactory.factories[clazz].create(readername)
@staticmethod
def readers(groups=None):
"""Return the list of readers"""
if groups is None:
groups = []
zreaders = []
for fm in ReaderFactory.factorymethods:
zreaders += fm(groups)
return zreaders
@@ -0,0 +1,105 @@
"""ReaderGroups manages smart card reader in 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.Exceptions import SmartcardException
from smartcard.ulist import ulist
# pylint: disable=too-few-public-methods
class BadReaderGroupException(SmartcardException):
"""Raised when trying to add an invalid reader group."""
def __init__(self):
SmartcardException.__init__(self, "Invalid reader group")
class innerreadergroups(ulist):
"""Smartcard readers groups private class.
The readergroups singleton manages the creation of the unique
instance of this class.
"""
def __init__(self, initlist=None):
"""Retrieve and store list of reader groups"""
if initlist is None:
initlist = self.getreadergroups() or []
ulist.__init__(self, initlist)
self.unremovablegroups = []
def __onadditem__(self, item):
"""Called when a reader group is added."""
self.addreadergroup(item)
def __onremoveitem__(self, item):
"""Called when a reader group is added."""
self.removereadergroup(item)
def __iter__(self):
return ulist.__iter__(self)
#
# abstract methods implemented in subclasses
#
def getreadergroups(self):
"""Returns the list of smartcard reader groups."""
return []
def addreadergroup(self, newgroup):
"""Add a reader group"""
if not isinstance(newgroup, str):
raise BadReaderGroupException
self += newgroup
def removereadergroup(self, group):
"""Remove a reader group"""
if not isinstance(group, str):
raise BadReaderGroupException
self.remove(group)
def addreadertogroup(self, readername, groupname):
"""Add a reader to a reader group"""
def removereaderfromgroup(self, readername, groupname):
"""Remove a reader from a reader group"""
class readergroups:
"""ReadersGroups organizes smart card reader as groups."""
# The single instance of __readergroups
instance = None
innerclazz = innerreadergroups
def __init__(self, initlist=None):
"""Create a single instance of innerreadergroups on first call"""
if readergroups.instance is None:
readergroups.instance = self.innerclazz(initlist)
# All operators redirected to inner class.
def __getattr__(self, name):
return getattr(self.instance, name)
@@ -0,0 +1,3 @@
"""Import symbols from internal smartcard.scard"""
from smartcard.scard.scard import *
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,49 @@
"""Base class for status word error checkers.
__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
"""
# pylint: disable=too-few-public-methods
class ErrorChecker:
"""Base class for status word error checking strategies.
Error checking strategies are chained into an L{ErrorCheckingChain} to
implement a Chain of Responsibility. Each strategy in the chain is
called until an error is detected. The strategy raises a
L{smartcard.sw.SWExceptions} exception when an error is detected.
Implementation derived from Bruce Eckel, Thinking in Python. The
L{ErrorCheckingChain} implements the Chain Of Responsibility design
pattern.
"""
def __call__(self, data, sw1, sw2):
"""Called to test data, sw1 and sw2 for error.
Derived classes must raise a L{smartcard.sw.SWExceptions} upon error.
@param data: apdu response data
@param sw1: apdu data status word 1
@param sw2: apdu data status word 2
"""
@@ -0,0 +1,96 @@
"""The error checking chain is a list of status word
(sw1, sw2) error check strategies.
__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 sys import exc_info
class ErrorCheckingChain:
"""The error checking chain is a list of response apdu status word
(sw1, sw2) error check strategies. Each strategy in the chain is
called until an error is detected. A L{smartcard.sw.SWExceptions}
exception is raised when an error is detected. No exception is
raised if no error is detected.
Implementation derived from Bruce Eckel, Thinking in Python. The
L{ErrorCheckingChain} implements the Chain Of Responsibility design
pattern.
"""
def __init__(self, chain, strategy):
"""constructor. Appends a strategy to the L{ErrorCheckingChain}
chain."""
self.strategy = strategy
self.chain = chain
self.chain.append(self)
self.excludes = []
def next(self):
"""Returns next error checking strategy."""
# Where this link is in the chain:
location = self.chain.index(self)
if not self.end():
return self.chain[location + 1]
return None
def addFilterException(self, exClass):
"""Add an exception filter to the error checking chain.
@param exClass: the exception to exclude, e.g.
L{smartcard.sw.SWExceptions.WarningProcessingException} A filtered
exception will not be raised when the sw1,sw2 conditions that
would raise the exception are met.
"""
self.excludes.append(exClass)
if self.end():
return
self.next().addFilterException(exClass)
def end(self):
"""Returns True if this is the end of the error checking
strategy chain."""
return self.chain.index(self) + 1 >= len(self.chain)
def __call__(self, data, sw1, sw2):
"""Called to test data, sw1 and sw2 for error on the chain."""
try:
self.strategy(data, sw1, sw2)
except tuple(self.excludes):
# The following additional filter may look redundant, it isn't.
# It checks that type(exc) is *equal* to any of self.excludes,
# rather than equal-or-subclass to any of self.excludes.
# This maintains backward compatibility with the behaviour of
# pyscard <= 1.6.16.
# if exception is filtered, return
for exception in self.excludes:
if exception == exc_info()[0]:
return None
# otherwise reraise exception
raise
# if not done, call next strategy
if self.end():
return None
return self.next()(data, sw1, sw2)
@@ -0,0 +1,183 @@
"""ISO7816-4 error checking strategy.
__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 smartcard.sw.SWExceptions
from smartcard.sw.ErrorChecker import ErrorChecker
# pylint: disable=too-few-public-methods
iso7816_4SW = {
0x62: (
smartcard.sw.SWExceptions.WarningProcessingException,
{
0x00: "Response padded/ More APDU commands expected",
0x81: "Part of returned data may be corrupted",
0x82: "End of file/record reached before reading Le bytes",
0x83: "File invalidated",
0x84: "FCI not correctly formatted",
0xFF: "Correct execution, response padded",
},
),
0x63: (
smartcard.sw.SWExceptions.WarningProcessingException,
{
0x00: "Authentication failed",
0x81: "File filled up by the last write",
0xC0: "PIN verification failed. 0 tries before blocking PIN",
0xC1: "PIN verification failed. 1 tries before blocking PIN",
0xC2: "PIN verification failed. 2 tries before blocking PIN",
0xC3: "PIN verification failed. 3 tries before blocking PIN",
0xC4: "PIN verification failed. 4 tries before blocking PIN",
0xC5: "PIN verification failed. 5 tries before blocking PIN",
0xC6: "PIN verification failed. 6 tries before blocking PIN",
0xC7: "PIN verification failed. 7 tries before blocking PIN",
0xC8: "PIN verification failed. 8 tries before blocking PIN",
0xC9: "PIN verification failed. 9 tries before blocking PIN",
0xCA: "PIN verification failed. 10 tries before blocking PIN",
0xCB: "PIN verification failed. 11 tries before blocking PIN",
0xCC: "PIN verification failed. 12 tries before blocking PIN",
0xCD: "PIN verification failed. 13 tries before blocking PIN",
0xCE: "PIN verification failed. 14 tries before blocking PIN",
0xCF: "PIN verification failed. 15 tries before blocking PIN",
},
),
0x64: (
smartcard.sw.SWExceptions.ExecutionErrorException,
{0x00: "Integrity error detected in EEPROM"},
),
0x67: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x00: "Wrong length in Lc"},
),
0x68: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x81: "Logical channel not supported", 0x82: "Secure messaging not supported"},
),
0x69: (
smartcard.sw.SWExceptions.CheckingErrorException,
{
0x81: "Command incompatible with file structure.",
0x82: "Security status not satisfied",
0x83: "Authentication method blocked",
0x84: "Referenced data invalid",
0x85: "Conditions of use not satisfied",
0x86: "Command not allowed (no current EF)",
0x87: "Secure messaging data object missing.",
0x88: "Secure messaging data object incorrect",
},
),
0x6A: (
smartcard.sw.SWExceptions.CheckingErrorException,
{
0x80: "Incorrect parameters in the data field",
0x81: "Function not supported",
0x82: "File not found",
0x83: "Record not found",
0x84: "Not enough memory space in the file",
0x85: "Lc inconsistent with TLV structure",
0x86: "Incorrect parameters P1-P2",
0x87: "Lc is inconsistent with P1-P2",
0x88: "Referenced data not found",
},
),
0x6B: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x00: "Incorrect parameters P1-P2"},
),
0x6D: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x00: "Instruction (INS) not supported"},
),
0x6E: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x00: "Class (CLA) not supported"},
),
0x6F: (smartcard.sw.SWExceptions.CheckingErrorException, {0x00: "Fatal error"}),
}
class ISO7816_4ErrorChecker(ErrorChecker):
"""ISO7816-4 error checking strategy.
This strategy raises the following exceptions:
- sw1 sw2
- 62 00 81 82 83 84 FF WarningProcessingException
- 63 00 81 C0->CF WarningProcessingException
- 64 00 ExecutionErrorException
- 67 00 CheckingErrorException
- 68 81 82 CheckingErrorException
- 69 81->88 99? c1? CheckingErrorException
- 6a 80->88 CheckingErrorException
- 6b 00 CheckingErrorException
- 6d 00 CheckingErrorException
- 6e 00 CheckingErrorException
- 6f 00 CheckingErrorException
This checker does not raise exceptions on undefined sw1 values, e.g.:
- sw1 sw2
- 65 any
- 66 any
- 6c any
and on undefined sw2 values, e.g.:
- sw1 sw2
- 62 80 85
- 6b any except 00
Use another checker in the error checking chain, e.g., the
ISO7816_4SW1ErrorChecker, to raise exceptions on these undefined
values.
"""
def __call__(self, data, sw1, sw2):
"""Called to test data, sw1 and sw2 for error.
Derived classes must raise a L{smartcard.sw.SWExceptions} upon error.
@param data: apdu response data
@param sw1: apdu data status words
@param sw2: apdu data status words
"""
# pylint: disable=duplicate-code
if sw1 in iso7816_4SW:
exception, sw2dir = iso7816_4SW[sw1]
if isinstance(sw2dir, dict):
try:
message = sw2dir[sw2]
raise exception(data, sw1, sw2, message)
except KeyError:
pass
if __name__ == "__main__":
# Small sample illustrating the use of ISO7816_4ErrorChecker.
ecs = ISO7816_4ErrorChecker()
ecs([], 0x90, 0x00)
try:
ecs([], 0x6B, 0x00)
except smartcard.sw.SWExceptions.CheckingErrorException as e:
print(str(e) + f" {e.sw1:x} {e.sw2:x}")
@@ -0,0 +1,88 @@
"""ISO7816-4 sw1 only error checker.
__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 smartcard.sw.SWExceptions
from smartcard.sw.ErrorChecker import ErrorChecker
# pylint: disable=too-few-public-methods
iso7816_4SW1 = {
0x62: smartcard.sw.SWExceptions.WarningProcessingException,
0x63: smartcard.sw.SWExceptions.WarningProcessingException,
0x64: smartcard.sw.SWExceptions.ExecutionErrorException,
0x65: smartcard.sw.SWExceptions.ExecutionErrorException,
0x66: smartcard.sw.SWExceptions.SecurityRelatedException,
0x67: smartcard.sw.SWExceptions.CheckingErrorException,
0x68: smartcard.sw.SWExceptions.CheckingErrorException,
0x69: smartcard.sw.SWExceptions.CheckingErrorException,
0x6A: smartcard.sw.SWExceptions.CheckingErrorException,
0x6B: smartcard.sw.SWExceptions.CheckingErrorException,
0x6C: smartcard.sw.SWExceptions.CheckingErrorException,
0x6D: smartcard.sw.SWExceptions.CheckingErrorException,
0x6E: smartcard.sw.SWExceptions.CheckingErrorException,
0x6F: smartcard.sw.SWExceptions.CheckingErrorException,
}
class ISO7816_4_SW1ErrorChecker(ErrorChecker):
"""ISO7816-4 error checker based on status word sw1 only.
This error checker raises the following exceptions:
- sw1 sw2
- 62 any L{WarningProcessingException}
- 63 any L{WarningProcessingException}
- 64 any L{ExecutionErrorException}
- 65 any L{ExecutionErrorException}
- 66 any L{SecurityRelatedException}
- 67 any L{CheckingErrorException}
- 68 any L{CheckingErrorException}
- 69 any L{CheckingErrorException}
- 6a any L{CheckingErrorException}
- 6b any L{CheckingErrorException}
- 6c any L{CheckingErrorException}
- 6d any L{CheckingErrorException}
- 6e any L{CheckingErrorException}
- 6f any L{CheckingErrorException}
"""
def __call__(self, data, sw1, sw2):
"""Called to test data, sw1 and sw2 for error.
@param data: apdu response data
@param sw1: apdu data status word 1
@param sw2: apdu data status word 2
"""
if sw1 in iso7816_4SW1:
exception = iso7816_4SW1[sw1]
raise exception(data, sw1, sw2)
if __name__ == "__main__":
# Small sample illustrating the use of ISO7816_4_SW1ErrorChecker.
ecs = ISO7816_4_SW1ErrorChecker()
ecs([], 0x90, 0x00)
try:
ecs([], 0x66, 0x80)
except smartcard.sw.SWExceptions.SecurityRelatedException as e:
print(str(e) + f" {e.sw1:x} {e.sw2:x}")
@@ -0,0 +1,153 @@
"""ISO7816-8 error checker.
__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 smartcard.sw.SWExceptions
from smartcard.sw.ErrorChecker import ErrorChecker
# pylint: disable=too-few-public-methods
iso7816_8SW = {
0x63: (
smartcard.sw.SWExceptions.WarningProcessingException,
{
0x00: "Authentication failed",
0xC0: "PIN verification failed. 0 retries before blocking PIN",
0xC1: "PIN verification failed. 1 retries before blocking PIN",
0xC2: "PIN verification failed. 2 retries before blocking PIN",
0xC3: "PIN verification failed. 3 retries before blocking PIN",
0xC4: "PIN verification failed. 4 retries before blocking PIN",
0xC5: "PIN verification failed. 5 retries before blocking PIN",
0xC6: "PIN verification failed. 6 retries before blocking PIN",
0xC7: "PIN verification failed. 7 retries before blocking PIN",
0xC8: "PIN verification failed. 8 retries before blocking PIN",
0xC9: "PIN verification failed. 9 retries before blocking PIN",
0xCA: "PIN verification failed. 10 retries before blocking PIN",
0xCB: "PIN verification failed. 11 retries before blocking PIN",
0xCC: "PIN verification failed. 12 retries before blocking PIN",
0xCD: "PIN verification failed. 13 retries before blocking PIN",
0xCE: "PIN verification failed. 14 retries before blocking PIN",
0xCF: "PIN verification failed. 15 retries before blocking PIN",
},
),
0x65: (
smartcard.sw.SWExceptions.ExecutionErrorException,
{0x81: "Memory failure (unsuccessful changing)"},
),
0x66: (
smartcard.sw.SWExceptions.SecurityRelatedException,
{
0x00: "The environment cannot be set or modified",
0x87: "Expected SM data objects missing",
0x88: "SM data objects incorrect",
},
),
0x67: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x00: "Wrong length (empty Lc field)"},
),
0x68: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x83: "Final command expected", 0x84: "Command chaining not supported"},
),
0x69: (
smartcard.sw.SWExceptions.CheckingErrorException,
{
0x82: "Security status not satisfied",
0x83: "Authentication method blocked",
0x84: "Referenced data invalidated",
0x85: "Conditions of use not satisfied",
},
),
0x6A: (
smartcard.sw.SWExceptions.CheckingErrorException,
{
0x81: "Function not supported",
0x82: "File not found",
0x86: "Incorrect parameters P1-P2",
0x88: "Referenced data not found",
},
),
}
class ISO7816_8ErrorChecker(ErrorChecker):
"""ISO7816-8 error checker.
This error checker raises the following exceptions:
- sw1 sw2
- 63 00,c0-cf L{WarningProcessingException}
- 65 81 L{ExecutionErrorException}
- 66 00,87,88 L{SecurityRelatedException}
- 67 00 L{CheckingErrorException}
- 68 82,84 L{CheckingErrorException}
- 69 82,83,84,85 L{CheckingErrorException}
- 6A 81,82,86,88 L{CheckingErrorException}
This checker does not raise exceptions on undefined sw1 values, e.g.:
- sw1 sw2
- 62 any
- 6f any
and on undefined sw2 values, e.g.:
- sw1 sw2
- 66 81 82
- 67 any except 00
Use another checker in the error checking chain, e.g., the
L{ISO7816_4_SW1ErrorChecker} or L{ISO7816_4ErrorChecker}, to raise
exceptions on these undefined values.
"""
def __call__(self, data, sw1, sw2):
"""Called to test data, sw1 and sw2 for error.
Derived classes must raise a L{smartcard.sw.SWExceptions} upon error.
@param data: apdu response data
@param sw1: apdu data status word 1
@param sw2: apdu data status word 2
"""
# pylint: disable=duplicate-code
if sw1 in iso7816_8SW:
exception, sw2dir = iso7816_8SW[sw1]
if isinstance(sw2dir, dict):
try:
message = sw2dir[sw2]
raise exception(data, sw1, sw2, message)
except KeyError:
pass
if __name__ == "__main__":
# Small sample illustrating the use of ISO7816_8ErrorChecker.
ecs = ISO7816_8ErrorChecker()
ecs([], 0x90, 0x00)
ecs([], 0x6A, 0x83)
try:
ecs([], 0x66, 0x87)
except smartcard.sw.SWExceptions.SecurityRelatedException as e:
print(str(e) + f" {e.sw1:x} {e.sw2:x}")
@@ -0,0 +1,111 @@
"""ISO7816-9 error checker.
__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 smartcard.sw.SWExceptions
from smartcard.sw.ErrorChecker import ErrorChecker
# pylint: disable=too-few-public-methods
iso7816_9SW = {
0x62: (
smartcard.sw.SWExceptions.WarningProcessingException,
{0x82: "End of file/record reached"},
),
0x64: (
smartcard.sw.SWExceptions.ExecutionErrorException,
{0x00: "Execution error"},
),
0x69: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x82: "Security status not satisfied"},
),
0x6A: (
smartcard.sw.SWExceptions.CheckingErrorException,
{
0x80: "Incorrect parameters in data field",
0x84: "Not enough memory space",
0x89: "File already exists",
0x8A: "DF name already exists",
},
),
}
class ISO7816_9ErrorChecker(ErrorChecker):
"""ISO7816-8 error checker.
This error checker raises the following exceptions:
- sw1 sw2
- 62 82 WarningProcessingException
- 64 00 ExecutionErrorException
- 69 82 CheckingErrorException
- 6A 80,84,89,8A CheckingErrorException
This checker does not raise exceptions on undefined sw1 values, e.g.:
- sw1 sw2
- 63 any
- 6F any
and on undefined sw2 values, e.g.:
- sw1 sw2
- 62 81 83
- 64 any except 00
Use another checker in the error checking chain, e.g., the
L{ISO7816_4_SW1ErrorChecker} or L{ISO7816_4ErrorChecker}, to raise
exceptions on these undefined values.
"""
def __call__(self, data, sw1, sw2):
"""Called to test data, sw1 and sw2 for error.
Derived classes must raise a L{smartcard.sw.SWExceptions} upon error.
@param data: apdu response data
@param sw1: apdu data status word 1
@param sw2: apdu data status word 2
"""
# pylint: disable=duplicate-code
if sw1 in iso7816_9SW:
exception, sw2dir = iso7816_9SW[sw1]
if isinstance(sw2dir, dict):
try:
message = sw2dir[sw2]
raise exception(data, sw1, sw2, message)
except KeyError:
pass
if __name__ == "__main__":
# Small sample illustrating the use of ISO7816_9ErrorChecker.
ecs = ISO7816_9ErrorChecker()
ecs([], 0x90, 0x00)
ecs([], 0x6A, 0x81)
try:
ecs([], 0x6A, 0x8A)
except smartcard.sw.SWExceptions.CheckingErrorException as e:
print(str(e) + f" {e.sw1:x} {e.sw2:x}")
@@ -0,0 +1,78 @@
"""Status Word (SW) Exceptions
This module defines the exceptions raised by status word errors or warnings.
__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
"""
class SWException(Exception):
"""Base class for status word exceptions.
Status word exceptions are generated when errors and warnings are detected
in the sw1 and sw2 bytes of the response apdu.
"""
def __init__(self, data, sw1, sw2, message=""):
self.message = message
"""response apdu data"""
self.data = data
"""response apdu sw1"""
self.sw1 = sw1
"""response apdu sw2"""
self.sw2 = sw2
def __str__(self):
return repr("Status word exception: " + self.message + "!")
class WarningProcessingException(SWException):
"""Raised when a warning processing is detected from sw1, sw2.
Examples of warning processing exception: sw1=62 or sw=63 (ISO7816-4)."""
def __init__(self, data, sw1, sw2, message=""):
SWException.__init__(self, data, sw1, sw2, "warning processing - " + message)
class ExecutionErrorException(SWException):
"""Raised when an execution error is detected from sw1, sw2.
Examples of execution error: sw1=64 or sw=65 (ISO7816-4)."""
def __init__(self, data, sw1, sw2, message=""):
SWException.__init__(self, data, sw1, sw2, "execution error - " + message)
class SecurityRelatedException(SWException):
"""Raised when a security issue is detected from sw1, sw2.
Examples of security issue: sw1=66 (ISO7816-4)."""
def __init__(self, data, sw1, sw2, message=""):
SWException.__init__(self, data, sw1, sw2, "security issue - " + message)
class CheckingErrorException(SWException):
"""Raised when a checking error is detected from sw1, sw2.
Examples of checking error: sw1=67 to 6F (ISO781604)."""
def __init__(self, data, sw1, sw2, message=""):
SWException.__init__(self, data, sw1, sw2, "checking error - " + message)
@@ -0,0 +1,23 @@
"""smartcard.sw module for status word error checking.
__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
"""
@@ -0,0 +1,139 @@
"""Open Platform 2.1 error checker.
__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 smartcard.sw.SWExceptions
from smartcard.sw.ErrorChecker import ErrorChecker
# pylint: disable=too-few-public-methods
op21_SW = {
0x62: (
smartcard.sw.SWExceptions.WarningProcessingException,
{0x83: "Card life cycle is CARD_LOCKED"},
),
0x63: (
smartcard.sw.SWExceptions.WarningProcessingException,
{0x00: "Authentication failed"},
),
0x64: (
smartcard.sw.SWExceptions.ExecutionErrorException,
{0x00: "Execution error"},
),
0x65: (smartcard.sw.SWExceptions.ExecutionErrorException, {0x81: "Memory failure"}),
0x67: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x00: "Wrong length in Lc"},
),
0x69: (
smartcard.sw.SWExceptions.CheckingErrorException,
{
0x82: "Security status not satisfied",
0x85: "Conditions of use not satisfied",
},
),
0x6A: (
smartcard.sw.SWExceptions.CheckingErrorException,
{
0x80: "Incorrect values in command data",
0x81: "Function not supported",
0x82: "Application not found",
0x84: "Not enough memory space",
0x86: "Incorrect parameters P1-P2",
0x88: "Referenced data not found",
},
),
0x6D: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x00: "Instruction not supported"},
),
0x6E: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x00: "Class not supported"},
),
0x94: (
smartcard.sw.SWExceptions.CheckingErrorException,
{0x84: "Algorithm not supported", 0x85: "Invalid key check value"},
),
}
class op21_ErrorChecker(ErrorChecker):
"""Open platform 2.1 error checker.
This error checker raises the following exceptions:
- sw1 sw2
- 62 83 L{WarningProcessingException}
- 63 00 L{WarningProcessingException}
- 64 00 L{ExecutionErrorException}
- 65 81 L{ExecutionErrorException}
- 67 00 L{CheckingErrorException}
- 69 82 85 L{CheckingErrorException}
- 6A 80 81 82 84 86 88 L{CheckingErrorException}
- 6D 00 L{CheckingErrorException}
- 6E 00 L{CheckingErrorException}
- 94 84 85 L{CheckingErrorException}
This checker does not raise exceptions on undefined sw1 values, e.g.:
- sw1 sw2
- 63 any
- 6F any
and on undefined sw2 values, e.g.:
- sw1 sw2
- 62 81 83
- 64 any except 00
Use another checker in the error checking chain to raise exceptions
on these undefined values.
"""
def __call__(self, data, sw1, sw2):
"""Called to test data, sw1 and sw2 for error.
Derived classes must raise a L{smartcard.sw.SWExceptions} upon error.
@param data: apdu response data
@param sw1: apdu data status word 1
@param sw2: apdu data status word 2
"""
if sw1 in op21_SW:
exception, sw2dir = op21_SW[sw1]
if isinstance(sw2dir, dict):
try:
message = sw2dir[sw2]
raise exception(data, sw1, sw2, message)
except KeyError:
pass
if __name__ == "__main__":
# Small sample illustrating the use of op21_ErrorChecker.
ecs = op21_ErrorChecker()
ecs([], 0x90, 0x00)
ecs([], 0x94, 0x81)
try:
ecs([], 0x94, 0x84)
except smartcard.sw.SWExceptions.CheckingErrorException as e:
print(str(e) + f"{e.sw1:x} {e.sw2:x}")
@@ -0,0 +1,122 @@
"""ulist is a subclass of list where items cannot appear twice in the list.
[1,2,2,3,3,4] is a valid list, whereas in ulist we can only have [1,2,3,4].
__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
"""
class ulist(list):
"""ulist ensures that all items are unique and provides an __onadditem__
hook to perform custom action in subclasses."""
#
# override list methods
#
def __init__(self, initlist=None):
if initlist is not None and initlist != []:
list.__init__(self, [initlist[0]])
for item in initlist[1:]:
if not list.__contains__(self, item):
list.append(self, item)
else:
list.__init__(self, initlist)
def __add__(self, other):
newother = self.__remove_duplicates(other)
self.__appendother__(newother)
return self.__class__(list(self) + list(newother))
def __iadd__(self, other):
newother = self.__remove_duplicates(other)
self.__appendother__(newother)
list.__iadd__(self, newother)
return self
def __radd__(self, other):
newother = self.__remove_duplicates(other)
return list.__add__(self, newother)
def append(self, item):
if not list.__contains__(self, item):
list.append(self, item)
self.__onadditem__(item)
def insert(self, i, item):
if not list.__contains__(self, item):
list.insert(self, i, item)
self.__onadditem__(item)
def pop(self, i=-1):
item = list.pop(self, i)
self.__onremoveitem__(item)
return item
def remove(self, item):
list.remove(self, item)
self.__onremoveitem__(item)
#
# non list methods
#
def __remove_duplicates(self, _other):
"""Remove from other items already in list."""
if (
not isinstance(_other, type(self))
and not isinstance(_other, type(list))
and not isinstance(_other, list)
):
other = [_other]
else:
other = list(_other)
# remove items already in self
newother = []
for _ in range(0, len(other)):
item = other.pop(0)
if not list.__contains__(self, item):
newother.append(item)
# remove duplicate items in other
other = []
if newother:
other.append(newother[0])
for _ in range(1, len(newother)):
item = newother.pop()
if item not in other:
other.append(item)
return other
def __appendother__(self, other):
"""Append other to object."""
for item in other:
self.__onadditem__(item)
def __onadditem__(self, item):
"""Called for each item added. Override in subclasses for adding
custom action."""
def __onremoveitem__(self, item):
"""Called for each item removed. Override in subclasses for
adding custom action."""
@@ -0,0 +1,274 @@
"""smartcard.util package
__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 __future__ import annotations
import warnings
PACK = 1
HEX = 2
UPPERCASE = 4
COMMA = 8
def padd(bytelist: list[int], length: int, padding: str = "FF"):
"""Padds a byte list with a constant byte value (default is x0FF)
@param bytelist: the byte list to padd
@param length: the total length of the resulting byte list;
no padding if length is smaller than the byte list length
@param padding: padding value (default is 0xff)
@return: the padded bytelist
>>> padd([59, 101, 0, 0, 156, 17, 1, 1, 3], 16)
[59, 101, 0, 0, 156, 17, 1, 1, 3, 255, 255, 255, 255, 255, 255, 255]
>>> padd([59, 101, 0, 0, 156, 17, 1, 1, 3], 12, '80')
[59, 101, 0, 0, 156, 17, 1, 1, 3, 128, 128, 128]
>>> padd([59, 101, 0, 0, 156, 17, 1, 1, 3], 8)
[59, 101, 0, 0, 156, 17, 1, 1, 3]
"""
return bytelist + [int(padding, 16)] * (length - len(bytelist))
def toASCIIBytes(stringtoconvert: str) -> list[int]:
"""Convert a string to a list of UTF-8 encoded bytes.
@param stringtoconvert: the string to convert into a byte list
@return: a byte list of the ASCII codes of the string characters
L{toASCIIBytes()} is the reverse of L{toASCIIString()}
>>> toASCIIBytes("Number 101")
[78, 117, 109, 98, 101, 114, 32, 49, 48, 49]
"""
return list(stringtoconvert.encode("utf-8"))
def toASCIIString(bytelist: list[int]) -> str:
"""Convert a list of integers in the range ``[32, 127]`` to a string.
Integer values outside the range ``[32, 127]`` are replaced with a period.
@param bytelist: list of ASCII bytes to convert into a string
@return: a string from the ASCII code list
L{toASCIIString()} is the reverse of L{toASCIIBytes()}
>>> toASCIIString([0x4E,0x75,0x6D,0x62,0x65,0x72,0x20,0x31,0x30,0x31])
'Number 101'
>>> toASCIIString([0x01, 0x20, 0x80, 0x7E, 0xF0])
". .~."
"""
return "".join(chr(c) if 32 <= c <= 127 else "." for c in bytelist)
def toBytes(bytestring: str) -> list[int]:
"""Convert a string of hexadecimal characters to a list of integers.
@param bytestring: a byte string
>>> toBytes("3B 65 00 00 9C 11 01 01 03")
[59, 101, 0, 0, 156, 17, 1, 1, 3]
>>> toBytes("3B6500009C11010103")
[59, 101, 0, 0, 156, 17, 1, 1, 3]
>>> toBytes("3B6500 009C1101 0103")
[59, 101, 0, 0, 156, 17, 1, 1, 3]
"""
try:
return list(bytes.fromhex(bytestring))
except ValueError as exc:
raise TypeError("not a string representing a list of bytes") from exc
# GSM3.38 character conversion table.
__dic_GSM_3_38__ = {
"@": 0x00, # @ At symbol
"£": 0x01, # £ Britain pound symbol
"$": 0x02, # $ Dollar symbol
"¥": 0x03, # ¥ Yen symbol
"è": 0x04, # è e accent grave
"é": 0x05, # é e accent aigu
"ù": 0x06, # ù u accent grave
"ì": 0x07, # ì i accent grave
"ò": 0x08, # ò o accent grave
"Ç": 0x09, # Ç C majuscule cedille
"\n": 0x0A, # LF Line Feed
"Ø": 0x0B, # Ø O majuscule barré
"ø": 0x0C, # ø o minuscule barré
"\r": 0x0D, # CR Carriage Return
"Å": 0x0E, # Å Angstroem majuscule
"å": 0x0F, # å Angstroem minuscule
"Δ": 0x10, # Δ Greek letter delta
"_": 0x11, # underscore
"Φ": 0x12, # Φ Greek letter phi
"Γ": 0x13, # Γ Greek letter gamma
"Λ": 0x14, # Λ Greek letter lambda
"Ω": 0x15, # Ω Greek letter omega
"Π": 0x16, # Π Greek letter pi
"Ψ": 0x17, # Ψ Greek letter psi
"Σ": 0x18, # Σ Greek letter sigma
"Θ": 0x19, # Θ Greek letter theta
"Ξ": 0x1A, # Ξ Greek letter xi
# 0x1B maps to extension table
"Æ": 0x1C, # Æ majuscule ae
"æ": 0x1D, # æ minuscule ae
"ß": 0x1E, # ß s dur allemand
"É": 0x1F, # É majuscule é
" ": 0x20,
"!": 0x21,
'"': 0x22, # guillemet
"#": 0x23,
"¤": 0x24, # ¤ carré
#
# 0x25 ... 0x3F # % ... ?
#
"¡": 0x40, # ¡ point d'exclamation renversé
#
# 0x41 ... 0x5A # A ... Z
#
"Ä": 0x5B, # Ä majuscule A trema
"Ö": 0x5C, # Ö majuscule O trema
"Ñ": 0x5D, # Ñ majuscule N tilde espagnol
"Ü": 0x5E, # Ü majuscule U trema
"§": 0x5F, # § signe paragraphe
"¿": 0x60, # ¿ point interrogation renversé
#
# 0x61 ... 0x7A # a ... z
#
"ä": 0x7B, # ä minuscule a trema
"ö": 0x7C, # ö minuscule o trema
"ñ": 0x7D, # ñ minuscule n tilde espagnol
"ü": 0x7E, # ü minuscule u trema
"à": 0x7F, # à a accent grave
}
def toGSM3_38Bytes(stringtoconvert: str | bytes) -> list[int]:
"""Returns a list of bytes from a string using GSM 3.38 conversion table.
@param stringtoconvert: string to convert
@return: a list of bytes
>>> toGSM3_38Bytes("@ùPascal")
[0, 6, 80, 97, 115, 99, 97, 108]
"""
if isinstance(stringtoconvert, bytes):
stringtoconvert = stringtoconvert.decode("iso8859-1")
result = []
for char in stringtoconvert:
if ("%" <= char <= "?") or ("A" <= char <= "Z") or ("a" <= char <= "z"):
result.append(ord(char))
else:
result.append(__dic_GSM_3_38__[char])
return result
def toHexString(data: list[int] | None = None, output_format: int = 0) -> str:
"""Convert a list of integers to a formatted string of hexadecimal.
Integers larger than 255 will be truncated to two-byte hexadecimal pairs.
@param data: a list of bytes to stringify,
e.g. [59, 22, 148, 32, 2, 1, 0, 0, 13]
@param output_format: a logical OR of
- COMMA: add a comma between bytes
- HEX: add the 0x chars before bytes
- UPPERCASE: use 0X before bytes (need HEX)
- PACK: remove blanks
>>> vals = [0x3B, 0x65, 0x00, 0x00, 0x9C, 0x11, 0x01, 0x01, 0x03]
>>> toHexString(vals)
'3B 65 00 00 9C 11 01 01 03'
>>> toHexString(vals, COMMA)
'3B, 65, 00, 00, 9C, 11, 01, 01, 03'
>>> toHexString(vals, HEX)
'0x3B 0x65 0x00 0x00 0x9C 0x11 0x01 0x01 0x03'
>>> toHexString(vals, HEX | COMMA)
'0x3B, 0x65, 0x00, 0x00, 0x9C, 0x11, 0x01, 0x01, 0x03'
>>> toHexString(vals, PACK)
'3B6500009C11010103'
>>> toHexString(vals, HEX | UPPERCASE)
'0X3B 0X65 0X00 0X00 0X9C 0X11 0X01 0X01 0X03'
>>> toHexString(vals, HEX | UPPERCASE | COMMA)
'0X3B, 0X65, 0X00, 0X00, 0X9C, 0X11, 0X01, 0X01, 0X03'
"""
if not (data is None or isinstance(data, list)):
raise TypeError("not a list of bytes")
if not data:
return ""
pformat = "%-0.2X"
separator = ""
if COMMA & output_format:
separator = ","
if not PACK & output_format:
separator += " "
if HEX & output_format:
if UPPERCASE & output_format:
pformat = "0X" + pformat
else:
pformat = "0x" + pformat
return separator.join(pformat % (a & 0xFF) for a in data).rstrip()
def HexListToBinString(hexlist: list[int]) -> str:
"""Deprecated. Use `bytes(hexlist).decode("utf-8")` or similar.
>>> HexListToBinString([78, 117, 109, 98, 101, 114, 32, 49, 48, 49])
'Number 101'
"""
warnings.warn(
'Use `bytes(hexlist).decode("utf-8")` or similar.',
DeprecationWarning,
)
return bytes(hexlist).decode("utf-8")
def BinStringToHexList(binstring: str) -> list[int]:
"""Deprecated. Use `list(binstring.encode("utf-8"))` or similar.
>>> BinStringToHexList("Number 101")
[78, 117, 109, 98, 101, 114, 32, 49, 48, 49]
"""
warnings.warn(
'Use `list(binstring.encode("utf-8"))` or similar.',
DeprecationWarning,
)
return list(binstring.encode("utf-8"))
hl2bs = HexListToBinString
bs2hl = BinStringToHexList
@@ -0,0 +1,76 @@
"""
A wxValidator that matches APDU in hexadecimal such as::
A4 A0 00 00 02
A4A0000002
__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 re
import string
import wx
# a regexp to match ATRs and APDUs
hexbyte = "[0-9a-fA-F]{1,2}"
apduregexp = re.compile("((%s)[ ]*)*" % hexbyte)
class APDUHexValidator(wx.PyValidator):
"""A wxValidator that matches APDU in hexadecimal such as:
A4 A0 00 00 02
A4A0000002"""
def __init__(self):
wx.Validator.__init__(self)
self.Bind(wx.EVT_CHAR, self.OnChar)
def Clone(self):
return APDUHexValidator()
def Validate(self, win):
tc = self.GetWindow()
value = tc.GetValue()
if not apduregexp.match(value):
return False
return True
def OnChar(self, event):
key = event.GetKeyCode()
if wx.WXK_SPACE == key or chr(key) in string.hexdigits:
value = event.GetEventObject().GetValue() + chr(key)
if apduregexp.match(value):
event.Skip()
return
if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255:
event.Skip()
return
if not wx.Validator.IsSilent():
wx.Bell()
return
@@ -0,0 +1,83 @@
"""Graphical APDU Tracer.
__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
"""
# wxPython GUI modules (https://www.wxpython.org/)
import wx
from smartcard.CardConnectionObserver import CardConnectionObserver
from smartcard.util import toHexString
[
wxID_APDUTEXTCTRL,
] = [wx.NewId() for x in range(1)]
class APDUTracerPanel(wx.Panel, CardConnectionObserver):
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
boxsizer = wx.BoxSizer(wx.HORIZONTAL)
self.apdutextctrl = wx.TextCtrl(
self,
wxID_APDUTEXTCTRL,
"",
pos=wx.DefaultPosition,
style=wx.TE_MULTILINE | wx.TE_READONLY,
)
boxsizer.Add(self.apdutextctrl, 1, wx.EXPAND | wx.ALL, 5)
self.SetSizer(boxsizer)
self.SetAutoLayout(True)
self.Bind(wx.EVT_TEXT_MAXLEN, self.OnMaxLength, self.apdutextctrl)
def OnMaxLength(self, evt):
"""Reset text buffer when max length is reached."""
self.apdutextctrl.SetValue("")
evt.Skip()
def update(self, cardconnection, ccevent):
"""CardConnectionObserver callback."""
apduline = ""
if "connect" == ccevent.type:
apduline += "connecting to " + cardconnection.getReader()
elif "disconnect" == ccevent.type:
apduline += "disconnecting from " + cardconnection.getReader()
elif "command" == ccevent.type:
apduline += "> " + toHexString(ccevent.args[0])
elif "response" == ccevent.type:
if [] == ccevent.args[0]:
apduline += "< %-2X %-2X" % tuple(ccevent.args[-2:])
else:
apduline += (
"< "
+ toHexString(ccevent.args[0])
+ "%-2X %-2X" % tuple(ccevent.args[-2:])
)
self.apdutextctrl.AppendText(apduline + "\n")
@@ -0,0 +1,394 @@
"""wxPython panel display cards/readers as a TreeCtrl.
__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
"""
# smartcard imports
from threading import RLock
# wxPython GUI modules (https://www.wxpython.org/)
import wx
import smartcard.wx.SimpleSCardApp
from smartcard.CardMonitoring import CardMonitor, CardObserver
from smartcard.Exceptions import CardConnectionException, NoCardException
from smartcard.ReaderMonitoring import ReaderMonitor, ReaderObserver
from smartcard.util import toHexString
from smartcard.wx import ICO_READER, ICO_SMARTCARD
class BaseCardTreeCtrl(wx.TreeCtrl):
"""Base class for the smart card and reader tree controls."""
def __init__(
self,
parent,
ID=wx.NewId(),
pos=wx.DefaultPosition,
size=wx.DefaultSize,
style=0,
clientpanel=None,
):
"""Constructor. Initializes a smartcard or reader tree control."""
wx.TreeCtrl.__init__(
self, parent, ID, pos, size, wx.TR_SINGLE | wx.TR_NO_BUTTONS
)
self.clientpanel = clientpanel
self.parent = parent
isz = (16, 16)
il = wx.ImageList(isz[0], isz[1])
self.capindex = il.Add(
wx.ArtProvider.GetBitmap(wx.ART_HELP_BOOK, wx.ART_OTHER, isz)
)
self.fldrindex = il.Add(
wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, isz)
)
self.fldropenindex = il.Add(
wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, isz)
)
if None != ICO_SMARTCARD:
self.cardimageindex = il.Add(wx.Bitmap(ICO_SMARTCARD, wx.BITMAP_TYPE_ICO))
if None != ICO_READER:
self.readerimageindex = il.Add(wx.Bitmap(ICO_READER, wx.BITMAP_TYPE_ICO))
self.il = il
self.SetImageList(self.il)
def Repaint(self):
self.Refresh()
class CardTreeCtrl(BaseCardTreeCtrl):
"""The CardTreeCtrl monitors inserted cards and notifies the
application client dialog of any card activation."""
def __init__(
self,
parent,
ID=wx.NewId(),
pos=wx.DefaultPosition,
size=wx.DefaultSize,
style=0,
clientpanel=None,
):
"""Constructor. Create a smartcard tree control."""
BaseCardTreeCtrl.__init__(
self, parent, ID, pos, size, wx.TR_SINGLE | wx.TR_NO_BUTTONS, clientpanel
)
self.root = self.AddRoot("Smartcards")
self.SetItemData(self.root, None)
self.SetItemImage(self.root, self.fldrindex, wx.TreeItemIcon_Normal)
self.SetItemImage(self.root, self.fldropenindex, wx.TreeItemIcon_Expanded)
self.Expand(self.root)
def OnAddCards(self, addedcards):
"""Called when a card is inserted.
Adds a smart card to the smartcards tree."""
parentnode = self.root
for cardtoadd in addedcards:
childCard = self.AppendItem(parentnode, toHexString(cardtoadd.atr))
self.SetItemText(childCard, toHexString(cardtoadd.atr))
self.SetItemData(childCard, cardtoadd)
self.SetItemImage(childCard, self.cardimageindex, wx.TreeItemIcon_Normal)
self.SetItemImage(childCard, self.cardimageindex, wx.TreeItemIcon_Expanded)
self.Expand(childCard)
self.Expand(self.root)
self.EnsureVisible(self.root)
self.Repaint()
def OnRemoveCards(self, removedcards):
"""Called when a card is removed.
Removes a card from the tree."""
parentnode = self.root
for cardtoremove in removedcards:
(childCard, cookie) = self.GetFirstChild(parentnode)
while childCard.IsOk():
if self.GetItemText(childCard) == toHexString(cardtoremove.atr):
self.Delete(childCard)
(childCard, cookie) = self.GetNextChild(parentnode, cookie)
self.Expand(self.root)
self.EnsureVisible(self.root)
self.Repaint()
class ReaderTreeCtrl(BaseCardTreeCtrl):
"""The ReaderTreeCtrl monitors inserted cards and readers and notifies the
application client dialog of any card activation."""
def __init__(
self,
parent,
ID=wx.NewId(),
pos=wx.DefaultPosition,
size=wx.DefaultSize,
style=0,
clientpanel=None,
):
"""Constructor. Create a reader tree control."""
BaseCardTreeCtrl.__init__(
self, parent, ID, pos, size, wx.TR_SINGLE | wx.TR_NO_BUTTONS, clientpanel
)
self.mutex = RLock()
self.root = self.AddRoot("Smartcard Readers")
self.SetItemData(self.root, None)
self.SetItemImage(self.root, self.fldrindex, wx.TreeItemIcon_Normal)
self.SetItemImage(self.root, self.fldropenindex, wx.TreeItemIcon_Expanded)
self.Expand(self.root)
def AddATR(self, readernode, atr):
"""Add an ATR to a reader node."""
capchild = self.AppendItem(readernode, atr)
self.SetItemData(capchild, None)
self.SetItemImage(capchild, self.cardimageindex, wx.TreeItemIcon_Normal)
self.SetItemImage(capchild, self.cardimageindex, wx.TreeItemIcon_Expanded)
self.Expand(capchild)
return capchild
def GetATR(self, reader):
"""Return the ATR of the card inserted into the reader."""
atr = "no card inserted"
try:
if not type(reader) is str:
connection = reader.createConnection()
connection.connect()
atr = toHexString(connection.getATR())
connection.disconnect()
except NoCardException:
pass
except CardConnectionException:
pass
return atr
def OnAddCards(self, addedcards):
"""Called when a card is inserted.
Adds the smart card child to the reader node."""
self.mutex.acquire()
try:
parentnode = self.root
for cardtoadd in addedcards:
(childReader, cookie) = self.GetFirstChild(parentnode)
found = False
while childReader.IsOk() and not found:
if self.GetItemText(childReader) == str(cardtoadd.reader):
(childCard, cookie2) = self.GetFirstChild(childReader)
self.SetItemText(childCard, toHexString(cardtoadd.atr))
self.SetItemData(childCard, cardtoadd)
found = True
else:
(childReader, cookie) = self.GetNextChild(parentnode, cookie)
# reader was not found, add reader node
# this happens when card monitoring thread signals
# added cards before reader monitoring thread signals
# added readers
if not found:
childReader = self.AppendItem(parentnode, str(cardtoadd.reader))
self.SetItemData(childReader, cardtoadd.reader)
self.SetItemImage(
childReader, self.readerimageindex, wx.TreeItemIcon_Normal
)
self.SetItemImage(
childReader, self.readerimageindex, wx.TreeItemIcon_Expanded
)
childCard = self.AddATR(childReader, toHexString(cardtoadd.atr))
self.SetItemData(childCard, cardtoadd)
self.Expand(childReader)
self.Expand(self.root)
finally:
self.mutex.release()
self.EnsureVisible(self.root)
self.Repaint()
def OnAddReaders(self, addedreaders):
"""Called when a reader is inserted.
Adds the smart card reader to the smartcard readers tree."""
self.mutex.acquire()
try:
parentnode = self.root
for readertoadd in addedreaders:
# is the reader already here?
found = False
(childReader, cookie) = self.GetFirstChild(parentnode)
while childReader.IsOk() and not found:
if self.GetItemText(childReader) == str(readertoadd):
found = True
else:
(childReader, cookie) = self.GetNextChild(parentnode, cookie)
if not found:
childReader = self.AppendItem(parentnode, str(readertoadd))
self.SetItemData(childReader, readertoadd)
self.SetItemImage(
childReader, self.readerimageindex, wx.TreeItemIcon_Normal
)
self.SetItemImage(
childReader, self.readerimageindex, wx.TreeItemIcon_Expanded
)
self.AddATR(childReader, self.GetATR(readertoadd))
self.Expand(childReader)
self.Expand(self.root)
finally:
self.mutex.release()
self.EnsureVisible(self.root)
self.Repaint()
def OnRemoveCards(self, removedcards):
"""Called when a card is removed.
Removes the card from the tree."""
self.mutex.acquire()
try:
parentnode = self.root
for cardtoremove in removedcards:
(childReader, cookie) = self.GetFirstChild(parentnode)
found = False
while childReader.IsOk() and not found:
if self.GetItemText(childReader) == str(cardtoremove.reader):
(childCard, cookie2) = self.GetFirstChild(childReader)
self.SetItemText(childCard, "no card inserted")
found = True
else:
(childReader, cookie) = self.GetNextChild(parentnode, cookie)
self.Expand(self.root)
finally:
self.mutex.release()
self.EnsureVisible(self.root)
self.Repaint()
def OnRemoveReaders(self, removedreaders):
"""Called when a reader is removed.
Removes the reader from the smartcard readers tree."""
self.mutex.acquire()
try:
parentnode = self.root
for readertoremove in removedreaders:
(childReader, cookie) = self.GetFirstChild(parentnode)
while childReader.IsOk():
if self.GetItemText(childReader) == str(readertoremove):
self.Delete(childReader)
else:
(childReader, cookie) = self.GetNextChild(parentnode, cookie)
self.Expand(self.root)
finally:
self.mutex.release()
self.EnsureVisible(self.root)
self.Repaint()
class CardAndReaderTreePanel(wx.Panel):
"""Panel containing the smart card and reader tree controls."""
class _CardObserver(CardObserver):
"""Inner CardObserver. Gets notified of card insertion
removal by the CardMonitor."""
def __init__(self, cardtreectrl):
self.cardtreectrl = cardtreectrl
def update(self, observable, handlers):
"""CardObserver callback that is notified
when cards are added or removed."""
addedcards, removedcards = handlers
self.cardtreectrl.OnRemoveCards(removedcards)
self.cardtreectrl.OnAddCards(addedcards)
class _ReaderObserver(ReaderObserver):
"""Inner ReaderObserver. Gets notified of reader insertion/removal
by the ReaderMonitor."""
def __init__(self, readertreectrl):
self.readertreectrl = readertreectrl
def update(self, observable, handlers):
"""ReaderObserver callback that is notified when
readers are added or removed."""
addedreaders, removedreaders = handlers
self.readertreectrl.OnRemoveReaders(removedreaders)
self.readertreectrl.OnAddReaders(addedreaders)
def __init__(self, parent, appstyle, clientpanel):
"""Constructor. Create a smartcard and reader tree control on the
left-hand side of the application main frame.
@param parent: the tree panel parent
@param appstyle: a combination of the following styles (bitwise or |)
- TR_SMARTCARD: display a smartcard tree panel
- TR_READER: display a reader tree panel
- default is TR_DEFAULT = TR_SMARTCARD
@param clientpanel: the client panel to notify of smartcard and reader events
"""
wx.Panel.__init__(self, parent, -1, style=wx.WANTS_CHARS)
sizer = wx.BoxSizer(wx.VERTICAL)
# create the smartcard tree
if appstyle & smartcard.wx.SimpleSCardApp.TR_SMARTCARD:
self.cardtreectrl = CardTreeCtrl(self, clientpanel=clientpanel)
# create the smartcard insertion observer
self.cardtreecardobserver = self._CardObserver(self.cardtreectrl)
# register as a CardObserver; we will ge
# notified of added/removed cards
self.cardmonitor = CardMonitor()
self.cardmonitor.addObserver(self.cardtreecardobserver)
sizer.Add(self.cardtreectrl, flag=wx.EXPAND | wx.ALL, proportion=1)
# create the reader tree
if appstyle & smartcard.wx.SimpleSCardApp.TR_READER:
self.readertreectrl = ReaderTreeCtrl(self, clientpanel=clientpanel)
# create the reader insertion observer
self.readertreereaderobserver = self._ReaderObserver(self.readertreectrl)
# register as a ReaderObserver; we will ge
# notified of added/removed readers
self.readermonitor = ReaderMonitor()
self.readermonitor.addObserver(self.readertreereaderobserver)
# create the smartcard insertion observer
self.readertreecardobserver = self._CardObserver(self.readertreectrl)
# register as a CardObserver; we will get
# notified of added/removed cards
self.cardmonitor = CardMonitor()
self.cardmonitor.addObserver(self.readertreecardobserver)
sizer.Add(self.readertreectrl, flag=wx.EXPAND | wx.ALL, proportion=1)
self.SetSizer(sizer)
self.SetAutoLayout(True)
def OnDestroy(self, event):
"""Called on panel destruction."""
# deregister observers
if hasattr(self, "cardmonitor"):
self.cardmonitor.deleteObserver(self.cardtreecardobserver)
if hasattr(self, "readermonitor"):
self.readermonitor.deleteObserver(self.readertreereaderobserver)
self.cardmonitor.deleteObserver(self.readertreecardobserver)
event.Skip()
@@ -0,0 +1,102 @@
"""wxPython toolbar with reader icons implementing ReaderObserver.
__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
"""
import wx
from smartcard.ReaderMonitoring import ReaderMonitor, ReaderObserver
from smartcard.wx import ICO_READER, ICO_SMARTCARD
class ReaderComboBox(wx.ComboBox, ReaderObserver):
def __init__(self, parent):
"""Constructor. Registers as ReaderObserver to get
notifications of reader insertion/removal."""
wx.ComboBox.__init__(
self,
parent,
wx.NewId(),
size=(170, -1),
style=wx.CB_DROPDOWN | wx.CB_SORT,
choices=[],
)
# register as a ReaderObserver; we will get
# notified of added/removed readers
self.readermonitor = ReaderMonitor()
self.readermonitor.addObserver(self)
def update(self, observable, handlers):
"""Toolbar ReaderObserver callback that is notified when
readers are added or removed."""
addedreaders, removedreaders = handlers
for reader in addedreaders:
item = self.Append(str(reader))
self.SetClientData(item, reader)
for reader in removedreaders:
item = self.FindString(str(reader))
if wx.NOT_FOUND != item:
self.Delete(item)
selection = self.GetSelection()
# if wx.NOT_FOUND == selection:
# self.SetSelection(0)
class ReaderToolbar(wx.ToolBar):
"""ReaderToolbar. Contains controls to select a reader from a listbox
and connect to the cards."""
def __init__(self, parent):
"""Constructor, creating the reader toolbar."""
wx.ToolBar.__init__(
self,
parent,
pos=wx.DefaultPosition,
size=wx.DefaultSize,
style=wx.SIMPLE_BORDER | wx.TB_HORIZONTAL | wx.TB_FLAT | wx.TB_TEXT,
name="Reader Toolbar",
)
# create bitmaps for toolbar
tsize = (16, 16)
if None != ICO_READER:
bmpReader = wx.Bitmap(ICO_READER, wx.BITMAP_TYPE_ICO)
else:
bmpReader = wx.ArtProvider_GetBitmap(wx.ART_HELP_BOOK, wx.ART_OTHER, tsize)
if None != ICO_SMARTCARD:
bmpCard = wx.Bitmap(ICO_SMARTCARD, wx.BITMAP_TYPE_ICO)
else:
bmpCard = wx.ArtProvider_GetBitmap(wx.ART_HELP_BOOK, wx.ART_OTHER, tsize)
self.readercombobox = ReaderComboBox(self)
# create and add controls
self.AddSimpleTool(
10, bmpReader, "Select smart card reader", "Select smart card reader"
)
self.AddControl(self.readercombobox)
self.AddSeparator()
self.AddSimpleTool(20, bmpCard, "Connect to smartcard", "Connect to smart card")
self.AddSeparator()
self.Realize()
@@ -0,0 +1,93 @@
"""Simple wxPython wxApp for smartcard.
__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
"""
import wx
from smartcard.wx.SimpleSCardAppFrame import SimpleSCardAppFrame
TR_SMARTCARD = 0x001
TR_READER = 0x002
TB_SMARTCARD = 0x004
TB_READER = 0x008
PANEL_APDUTRACER = 0x010
TR_DEFAULT = TR_SMARTCARD
class SimpleSCardApp(wx.App):
"""The SimpleSCardApp class represents the smart card application.
SimpleSCardApp is a subclass of wx.App.
"""
def __init__(
self,
appname="",
apppanel=None,
appstyle=TR_DEFAULT,
appicon=None,
pos=(-1, -1),
size=(-1, -1),
):
r"""Constructor for simple smart card application.
@param appname: the application name
@param apppanel: the application panel to display in the application frame
@param appicon: the application icon file; the default is no icon
@param appstyle: a combination of the following styles (bitwise or |)
- TR_SMARTCARD: display a smartcard tree panel
- TR_READER: display a reader tree panel
- TB_SMARTCARD: display a smartcard toolbar
- TB_SMARTCARD: display a reader toolbar
- PANEL_APDUTRACER: display an APDU tracer panel
- default is TR_DEFAULT = TR_SMARTCARD
@param pos: the application position as a (x,y) tuple; default is (-1,-1)
@param size: the application window size as a (x,y) tuple; default is (-1,-1)
Example:
C{app = SimpleSCardApp(
appname = 'A simple smartcard application',
apppanel = testpanel.MyPanel,
appstyle = TR_READER | TR_SMARTCARD,
appicon = 'resources\mysmartcard.ico')}
"""
self.appname = appname
self.apppanel = apppanel
self.appstyle = appstyle
self.appicon = appicon
self.pos = pos
self.size = size
wx.App.__init__(self, False)
def OnInit(self):
"""Create and display application frame."""
self.frame = SimpleSCardAppFrame(
self.appname,
self.apppanel,
self.appstyle,
self.appicon,
self.pos,
self.size,
)
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
@@ -0,0 +1,63 @@
"""Smartcard event observer.
__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
"""
class SimpleSCardAppEventObserver:
"""This interface defines the event handlers
called by the SimpleSCardApp."""
def __init__(self):
self.selectedcard = None
self.selectedreader = None
# callbacks from SimpleCardAppFrame controls
def OnActivateCard(self, card):
"""Called when a card is activated in the reader
tree control or toolbar."""
self.selectedcard = card
def OnActivateReader(self, reader):
"""Called when a reader is activated in the reader
tree control or toolbar."""
self.selectedreader = reader
def OnDeactivateCard(self, card):
"""Called when a card is deactivated in the reader
tree control or toolbar."""
pass
def OnDeselectCard(self, card):
"""Called when a card is selected in the reader
tree control or toolbar."""
self.selectedcard = None
def OnSelectCard(self, card):
"""Called when a card is selected in the reader
tree control or toolbar."""
self.selectedcard = card
def OnSelectReader(self, reader):
"""Called when a reader is selected in the reader
tree control or toolbar."""
self.selectedreader = reader
@@ -0,0 +1,343 @@
"""Simple wxpython frame for smart card application.
__author__ = "gemalto https://www.gemalto.com/"
__date__ = "November 2006"
__version__ = "1.4.0"
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 os.path
import wx
import smartcard
import smartcard.wx
from smartcard.wx import APDUTracerPanel, CardAndReaderTreePanel, ReaderToolbar
from smartcard.wx.SimpleSCardAppEventObserver import SimpleSCardAppEventObserver
[
wxID_SIMPLESCARDAPP_FRAME,
] = [wx.NewId() for x in range(1)]
class BlankPanel(wx.Panel, SimpleSCardAppEventObserver):
"""A blank panel in case no panel is provided to SimpleSCardApp."""
def __init__(self, parent):
wx.Panel.__init__(self, parent, -1)
sizer = wx.GridSizer(1, 1, 0)
self.SetSizer(sizer)
self.SetAutoLayout(True)
class TreeAndUserPanelPanel(wx.Panel):
"""The panel that contains the Card/Reader TreeCtrl
and the user provided Panel."""
def __init__(self, parent, apppanelclass, appstyle):
"""
Constructor. Creates the panel with two panels:
- the left-hand panel is holding the smartcard and/or reader tree
- the right-hand panel is holding the application dialog
@param apppanelclass: the class of the panel to instantiate in the
L{SimpleSCardAppFrame}
@param appstyle: a combination of the following styles (bitwise or |)
- TR_SMARTCARD: display a smartcard tree panel
- TR_READER: display a reader tree panel
- TB_SMARTCARD: display a smartcard toolbar
- TB_SMARTCARD: display a reader toolbar
- default is TR_DEFAULT = TR_SMARTCARD
"""
wx.Panel.__init__(self, parent, -1)
self.parent = parent
self.selectedcard = None
boxsizer = wx.BoxSizer(wx.HORIZONTAL)
# create user dialog
if None != apppanelclass:
self.dialogpanel = apppanelclass(self)
else:
self.dialogpanel = BlankPanel(self)
# create card/reader tree control
if (
appstyle & smartcard.wx.SimpleSCardApp.TR_SMARTCARD
or appstyle & smartcard.wx.SimpleSCardApp.TR_READER
):
self.readertreepanel = CardAndReaderTreePanel.CardAndReaderTreePanel(
self, appstyle, self.dialogpanel
)
boxsizer.Add(self.readertreepanel, 1, wx.EXPAND | wx.ALL, 5)
boxsizer.Add(self.dialogpanel, 2, wx.EXPAND | wx.ALL)
if appstyle & smartcard.wx.SimpleSCardApp.TR_READER:
self.Bind(
wx.EVT_TREE_ITEM_ACTIVATED,
self.OnActivateReader,
self.readertreepanel.readertreectrl,
)
self.Bind(
wx.EVT_TREE_SEL_CHANGED,
self.OnSelectReader,
self.readertreepanel.readertreectrl,
)
self.Bind(
wx.EVT_TREE_ITEM_RIGHT_CLICK,
self.OnReaderRightClick,
self.readertreepanel.readertreectrl,
)
self.Bind(
wx.EVT_TREE_ITEM_COLLAPSED,
self.OnItemCollapsed,
self.readertreepanel.readertreectrl,
)
if appstyle & smartcard.wx.SimpleSCardApp.TR_SMARTCARD:
self.Bind(
wx.EVT_TREE_ITEM_ACTIVATED,
self.OnActivateCard,
self.readertreepanel.cardtreectrl,
)
self.Bind(
wx.EVT_TREE_SEL_CHANGED,
self.OnSelectCard,
self.readertreepanel.cardtreectrl,
)
self.Bind(
wx.EVT_TREE_ITEM_RIGHT_CLICK,
self.OnCardRightClick,
self.readertreepanel.cardtreectrl,
)
self.SetSizer(boxsizer)
self.SetAutoLayout(True)
def ActivateCard(self, card):
"""Activate a card."""
if not hasattr(card, "connection"):
card.connection = card.createConnection()
if None != self.parent.apdutracerpanel:
card.connection.addObserver(self.parent.apdutracerpanel)
card.connection.connect()
self.dialogpanel.OnActivateCard(card)
def DeactivateCard(self, card):
"""Deactivate a card."""
if hasattr(card, "connection"):
card.connection.disconnect()
if None != self.parent.apdutracerpanel:
card.connection.deleteObserver(self.parent.apdutracerpanel)
delattr(card, "connection")
self.dialogpanel.OnDeactivateCard(card)
def OnActivateCard(self, event):
"""Called when the user activates a card in the tree."""
item = event.GetItem()
if item:
itemdata = self.readertreepanel.cardtreectrl.GetItemData(item)
if isinstance(itemdata, smartcard.Card.Card):
self.ActivateCard(itemdata)
else:
self.dialogpanel.OnDeselectCard(itemdata)
def OnActivateReader(self, event):
"""Called when the user activates a reader in the tree."""
item = event.GetItem()
if item:
itemdata = self.readertreepanel.readertreectrl.GetItemData(item)
if isinstance(itemdata, smartcard.Card.Card):
self.ActivateCard(itemdata)
elif isinstance(itemdata, smartcard.reader.Reader.Reader):
self.dialogpanel.OnActivateReader(itemdata)
event.Skip()
def OnItemCollapsed(self, event):
item = event.GetItem()
self.readertreepanel.readertreectrl.Expand(item)
def OnCardRightClick(self, event):
"""Called when user right-clicks a node in the card tree control."""
item = event.GetItem()
if item:
itemdata = self.readertreepanel.cardtreectrl.GetItemData(item)
if isinstance(itemdata, smartcard.Card.Card):
self.selectedcard = itemdata
if not hasattr(self, "connectID"):
self.connectID = wx.NewId()
self.disconnectID = wx.NewId()
self.Bind(wx.EVT_MENU, self.OnConnect, id=self.connectID)
self.Bind(wx.EVT_MENU, self.OnDisconnect, id=self.disconnectID)
menu = wx.Menu()
if not hasattr(self.selectedcard, "connection"):
menu.Append(self.connectID, "Connect")
else:
menu.Append(self.disconnectID, "Disconnect")
self.PopupMenu(menu)
menu.Destroy()
def OnReaderRightClick(self, event):
"""Called when user right-clicks a node in the reader tree control."""
item = event.GetItem()
if item:
itemdata = self.readertreepanel.readertreectrl.GetItemData(item)
if isinstance(itemdata, smartcard.Card.Card):
self.selectedcard = itemdata
if not hasattr(self, "connectID"):
self.connectID = wx.NewId()
self.disconnectID = wx.NewId()
self.Bind(wx.EVT_MENU, self.OnConnect, id=self.connectID)
self.Bind(wx.EVT_MENU, self.OnDisconnect, id=self.disconnectID)
menu = wx.Menu()
if not hasattr(self.selectedcard, "connection"):
menu.Append(self.connectID, "Connect")
else:
menu.Append(self.disconnectID, "Disconnect")
self.PopupMenu(menu)
menu.Destroy()
def OnConnect(self, event):
if isinstance(self.selectedcard, smartcard.Card.Card):
self.ActivateCard(self.selectedcard)
def OnDisconnect(self, event):
if isinstance(self.selectedcard, smartcard.Card.Card):
self.DeactivateCard(self.selectedcard)
def OnSelectCard(self, event):
"""Called when the user selects a card in the tree."""
item = event.GetItem()
if item:
itemdata = self.readertreepanel.cardtreectrl.GetItemData(item)
if isinstance(itemdata, smartcard.Card.Card):
self.dialogpanel.OnSelectCard(itemdata)
else:
self.dialogpanel.OnDeselectCard(itemdata)
def OnSelectReader(self, event):
"""Called when the user selects a reader in the tree."""
item = event.GetItem()
if item:
itemdata = self.readertreepanel.readertreectrl.GetItemData(item)
if isinstance(itemdata, smartcard.Card.Card):
self.dialogpanel.OnSelectCard(itemdata)
elif isinstance(itemdata, smartcard.reader.Reader.Reader):
self.dialogpanel.OnSelectReader(itemdata)
else:
self.dialogpanel.OnDeselectCard(itemdata)
class SimpleSCardAppFrame(wx.Frame):
"""The main frame of the simple smartcard application."""
def __init__(
self,
appname,
apppanelclass,
appstyle,
appicon,
pos=(-1, -1),
size=(-1, -1),
):
"""
Constructor. Creates the frame with two panels:
- the left-hand panel is holding the smartcard and/or reader tree
- the right-hand panel is holding the application dialog
@param appname: name of the application
@param apppanelclass: the class of the panel to instantiate in the
L{SimpleSCardAppFrame}
@param appstyle: a combination of the following styles (bitwise or |)
- TR_SMARTCARD: display a smartcard tree panel
- TR_READER: display a reader tree panel
- TB_SMARTCARD: display a smartcard toolbar
- TB_SMARTCARD: display a reader toolbar
- PANEL_APDUTRACER: display an APDU tracer panel
- default is TR_DEFAULT = TR_SMARTCARD
@param pos: the application position as a (x,y) tuple; default is (-1,-1)
@param size: the application window size as a (x,y) tuple; default is (-1,-1)
"""
wx.Frame.__init__(
self,
None,
wxID_SIMPLESCARDAPP_FRAME,
appname,
pos=pos,
size=size,
style=wx.DEFAULT_FRAME_STYLE,
)
if appicon:
_icon = wx.Icon(appicon, wx.BITMAP_TYPE_ICO)
self.SetIcon(_icon)
elif os.path.exists(smartcard.wx.ICO_SMARTCARD):
_icon = wx.Icon(smartcard.wx.ICO_SMARTCARD, wx.BITMAP_TYPE_ICO)
self.SetIcon(_icon)
boxsizer = wx.BoxSizer(wx.VERTICAL)
self.treeuserpanel = TreeAndUserPanelPanel(self, apppanelclass, appstyle)
boxsizer.Add(self.treeuserpanel, 3, wx.EXPAND | wx.ALL)
# create a toolbar if required
if appstyle & smartcard.wx.SimpleSCardApp.TB_SMARTCARD:
self.toolbar = ReaderToolbar.ReaderToolbar(self)
self.SetToolBar(self.toolbar)
else:
self.toolbar = None
# create an apdu tracer console if required
if appstyle & smartcard.wx.SimpleSCardApp.PANEL_APDUTRACER:
self.apdutracerpanel = APDUTracerPanel.APDUTracerPanel(self)
boxsizer.Add(self.apdutracerpanel, 1, wx.EXPAND | wx.ALL)
else:
self.apdutracerpanel = None
self.SetSizer(boxsizer)
self.SetAutoLayout(True)
self.Bind(wx.EVT_CLOSE, self.OnCloseFrame)
if appstyle & smartcard.wx.SimpleSCardApp.TB_SMARTCARD:
self.Bind(
wx.EVT_COMBOBOX, self.OnReaderComboBox, self.toolbar.readercombobox
)
def OnCloseFrame(self, evt):
"""Called when frame is closed, i.e. on wx.EVT_CLOSE"""
evt.Skip()
def OnExit(self, evt):
"""Called when frame application exits."""
self.Close(True)
evt.Skip()
def OnReaderComboBox(self, event):
"""Called when the user activates a reader in the toolbar combo box."""
cb = event.GetEventObject()
reader = cb.GetClientData(cb.GetSelection())
if isinstance(reader, smartcard.reader.Reader.Reader):
self.treeuserpanel.dialogpanel.OnActivateReader(reader)
@@ -0,0 +1,49 @@
"""wxpython smartcard utility module.
__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 os.path
import sys
def main_is_frozen():
return hasattr(sys, "frozen") or hasattr(sys, "importers")
ICO_SMARTCARD = None
ICO_READER = None
# running from a script, i.e. not running from standalone exe built with py2exe
if not main_is_frozen():
ICO_SMARTCARD = os.path.join(
os.path.dirname(__file__), "resources", "smartcard.ico"
)
ICO_READER = os.path.join(os.path.dirname(__file__), "resources", "reader.ico")
# running from a standalone exe built with py2exe
# resources expected images directory
else:
if os.path.exists(os.path.join("images", "smartcard.ico")):
ICO_SMARTCARD = os.path.join("images", "smartcard.ico")
if os.path.exists(os.path.join("images", "reader.ico")):
ICO_READER = os.path.join("images", "reader.ico")
Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B