first init
This commit is contained in:
@@ -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 *
|
||||
BIN
Binary file not shown.
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 |
Reference in New Issue
Block a user