Smartcard library for Python providing PC/SC interface for smart card communication
—
wxPython-based graphical user interface components for building smart card applications with visual elements. These components provide ready-to-use GUI building blocks for smart card applications.
Basic utilities and constants for smart card GUI applications.
def main_is_frozen():
"""
Detect if the application is running from a frozen executable (py2exe, PyInstaller, etc.).
Returns:
bool: True if running from frozen executable, False otherwise
"""
# Icon constants
ICO_SMARTCARD: str # Path to smartcard icon resource
ICO_READER: str # Path to reader icon resourceBase classes for creating smart card applications with wxPython.
class SimpleSCardApp:
"""
Base class for simple smart card applications.
Provides basic application framework with card monitoring.
"""
class SimpleSCardAppFrame:
"""
Main application frame for smart card applications.
Provides standard menu bar and status bar.
"""
class SimpleSCardAppEventObserver:
"""
Event observer for smart card application events.
Handles card insertion/removal events in GUI context.
"""Ready-to-use wxPython panels and controls for smart card applications.
class APDUTracerPanel:
"""
Panel for displaying APDU command and response traces.
Provides formatted display of smart card communication.
Features:
- Command/response logging
- Hex and ASCII display modes
- Export functionality
- Search and filtering
"""
class CardAndReaderTreePanel:
"""
Tree control panel showing readers and inserted cards.
Provides hierarchical view of smart card system.
Features:
- Reader enumeration
- Card detection display
- ATR information
- Context menus
"""
class ReaderToolbar:
"""
Toolbar with common reader operations.
Provides quick access to reader functions.
Features:
- Reader selection
- Connect/disconnect buttons
- Status indicators
"""Specialized validators for smart card data input.
class APDUHexValidator:
"""
wxPython validator for APDU hexadecimal input.
Ensures only valid hex characters and proper APDU format.
Features:
- Hex character validation
- Automatic spacing
- Length checking
- Invalid character filtering
"""import wx
from smartcard.wx import SimpleSCardAppFrame, main_is_frozen
from smartcard.CardMonitoring import CardMonitor, CardObserver
from smartcard.util import toHexString
class MyCardObserver(CardObserver):
def __init__(self, frame):
self.frame = frame
def update(self, observable, actions):
"""Handle card insertion/removal events in GUI."""
(addedcards, removedcards) = actions
# Update GUI in main thread
wx.CallAfter(self.update_gui, addedcards, removedcards)
def update_gui(self, addedcards, removedcards):
for card in addedcards:
self.frame.log_message(f"Card inserted: {toHexString(card.atr)}")
for card in removedcards:
self.frame.log_message(f"Card removed: {toHexString(card.atr)}")
class SmartCardApp(wx.App):
def OnInit(self):
# Create main frame
frame = SimpleSCardAppFrame(None, "Smart Card Application")
# Set up card monitoring
self.monitor = CardMonitor()
self.observer = MyCardObserver(frame)
self.monitor.addObserver(self.observer)
frame.Show()
return True
def OnExit(self):
# Clean up monitoring
if hasattr(self, 'monitor'):
self.monitor.deleteObserver(self.observer)
# Application entry point
if __name__ == '__main__':
app = SmartCardApp()
app.MainLoop()import wx
from smartcard.wx import APDUTracerPanel
from smartcard import Session
from smartcard.util import toHexString
class APDUTracerFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="APDU Tracer", size=(800, 600))
# Create main panel
self.panel = wx.Panel(self)
# Create APDU tracer panel
self.tracer = APDUTracerPanel(self.panel)
# Create input panel
input_panel = wx.Panel(self.panel)
input_sizer = wx.BoxSizer(wx.HORIZONTAL)
wx.StaticText(input_panel, label="APDU:")
self.apdu_input = wx.TextCtrl(input_panel, value="00 A4 00 00")
send_btn = wx.Button(input_panel, label="Send")
send_btn.Bind(wx.EVT_BUTTON, self.on_send_apdu)
input_sizer.Add(wx.StaticText(input_panel, label="APDU:"), 0, wx.ALL|wx.CENTER, 5)
input_sizer.Add(self.apdu_input, 1, wx.ALL|wx.EXPAND, 5)
input_sizer.Add(send_btn, 0, wx.ALL, 5)
input_panel.SetSizer(input_sizer)
# Layout
main_sizer = wx.BoxSizer(wx.VERTICAL)
main_sizer.Add(self.tracer, 1, wx.ALL|wx.EXPAND, 5)
main_sizer.Add(input_panel, 0, wx.ALL|wx.EXPAND, 5)
self.panel.SetSizer(main_sizer)
# Initialize session
try:
self.session = Session()
self.tracer.add_trace("Session", "Connected to first reader")
except Exception as e:
self.tracer.add_trace("Error", f"Failed to connect: {e}")
self.session = None
def on_send_apdu(self, event):
"""Send APDU command and display in tracer."""
if not self.session:
self.tracer.add_trace("Error", "No session available")
return
try:
# Parse hex input
apdu_text = self.apdu_input.GetValue()
apdu_bytes = [int(x, 16) for x in apdu_text.split()]
# Send command
self.tracer.add_trace("Command", toHexString(apdu_bytes))
response, sw1, sw2 = self.session.sendCommandAPDU(apdu_bytes)
# Display response
if response:
self.tracer.add_trace("Response", f"{toHexString(response)} {sw1:02X} {sw2:02X}")
else:
self.tracer.add_trace("Response", f"{sw1:02X} {sw2:02X}")
except ValueError:
self.tracer.add_trace("Error", "Invalid hex input")
except Exception as e:
self.tracer.add_trace("Error", f"Command failed: {e}")
def __del__(self):
if hasattr(self, 'session') and self.session:
self.session.close()
class APDUTracerApp(wx.App):
def OnInit(self):
frame = APDUTracerFrame()
frame.Show()
return True
if __name__ == '__main__':
app = APDUTracerApp()
app.MainLoop()import wx
from smartcard.wx import CardAndReaderTreePanel
from smartcard.System import readers
from smartcard.CardMonitoring import CardMonitor, CardObserver
class ReaderCardFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="Readers and Cards", size=(400, 500))
# Create tree panel
self.tree_panel = CardAndReaderTreePanel(self)
# Create context menu
self.create_context_menu()
# Set up card monitoring
self.monitor = CardMonitor()
self.observer = TreeCardObserver(self.tree_panel)
self.monitor.addObserver(self.observer)
# Initial population
self.refresh_tree()
# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.tree_panel, 1, wx.EXPAND)
self.SetSizer(sizer)
def create_context_menu(self):
"""Create context menu for tree items."""
menu = wx.Menu()
menu.Append(wx.ID_REFRESH, "Refresh")
menu.Append(wx.ID_PROPERTIES, "Properties")
self.Bind(wx.EVT_MENU, self.on_refresh, id=wx.ID_REFRESH)
self.Bind(wx.EVT_MENU, self.on_properties, id=wx.ID_PROPERTIES)
self.tree_panel.Bind(wx.EVT_CONTEXT_MENU,
lambda evt: self.PopupMenu(menu))
def refresh_tree(self):
"""Refresh the reader/card tree."""
self.tree_panel.clear()
# Add readers
for reader in readers():
self.tree_panel.add_reader(reader)
# Try to detect cards
try:
connection = reader.createConnection()
connection.connect()
atr = connection.getATR()
self.tree_panel.add_card_to_reader(reader, atr)
connection.disconnect()
except:
pass # No card present
def on_refresh(self, event):
self.refresh_tree()
def on_properties(self, event):
selected_item = self.tree_panel.get_selected_item()
if selected_item:
# Show properties dialog
dlg = wx.MessageDialog(self, f"Properties: {selected_item}",
"Item Properties", wx.OK)
dlg.ShowModal()
dlg.Destroy()
def __del__(self):
if hasattr(self, 'monitor'):
self.monitor.deleteObserver(self.observer)
class TreeCardObserver(CardObserver):
def __init__(self, tree_panel):
self.tree_panel = tree_panel
def update(self, observable, actions):
(addedcards, removedcards) = actions
# Update tree in main thread
wx.CallAfter(self.update_tree, addedcards, removedcards)
def update_tree(self, addedcards, removedcards):
for card in addedcards:
self.tree_panel.add_card_to_reader(card.reader, card.atr)
for card in removedcards:
self.tree_panel.remove_card_from_reader(card.reader)
class ReaderCardApp(wx.App):
def OnInit(self):
frame = ReaderCardFrame()
frame.Show()
return True
if __name__ == '__main__':
app = ReaderCardApp()
app.MainLoop()import wx
from smartcard.wx import APDUHexValidator
class APDUInputFrame(wx.Frame):
def __init__(self):
super().__init__(None, title="APDU Input Validation", size=(500, 300))
panel = wx.Panel(self)
# Create validated text controls
wx.StaticText(panel, label="APDU Command (Hex):")
self.apdu_ctrl = wx.TextCtrl(panel, size=(300, -1))
self.apdu_ctrl.SetValidator(APDUHexValidator())
wx.StaticText(panel, label="Expected Response Length:")
self.len_ctrl = wx.TextCtrl(panel)
# Buttons
validate_btn = wx.Button(panel, label="Validate")
clear_btn = wx.Button(panel, label="Clear")
validate_btn.Bind(wx.EVT_BUTTON, self.on_validate)
clear_btn.Bind(wx.EVT_BUTTON, self.on_clear)
# Status
self.status = wx.StaticText(panel, label="Enter APDU command...")
# Layout
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(wx.StaticText(panel, label="APDU Command (Hex):"), 0, wx.ALL, 5)
sizer.Add(self.apdu_ctrl, 0, wx.ALL|wx.EXPAND, 5)
sizer.Add(wx.StaticText(panel, label="Expected Response Length:"), 0, wx.ALL, 5)
sizer.Add(self.len_ctrl, 0, wx.ALL, 5)
btn_sizer = wx.BoxSizer(wx.HORIZONTAL)
btn_sizer.Add(validate_btn, 0, wx.ALL, 5)
btn_sizer.Add(clear_btn, 0, wx.ALL, 5)
sizer.Add(btn_sizer, 0, wx.ALL, 5)
sizer.Add(self.status, 0, wx.ALL, 5)
panel.SetSizer(sizer)
def on_validate(self, event):
"""Validate APDU input."""
try:
apdu_text = self.apdu_ctrl.GetValue()
if not apdu_text.strip():
self.status.SetLabel("❌ Empty APDU")
return
# Parse hex bytes
bytes_list = [int(x, 16) for x in apdu_text.split()]
# Basic APDU validation
if len(bytes_list) < 4:
self.status.SetLabel("❌ APDU too short (minimum 4 bytes)")
return
if len(bytes_list) > 255:
self.status.SetLabel("❌ APDU too long (maximum 255 bytes)")
return
cla, ins, p1, p2 = bytes_list[:4]
# Validate structure
if len(bytes_list) == 4:
apdu_type = "Case 1 (no data, no response)"
elif len(bytes_list) == 5:
le = bytes_list[4]
apdu_type = f"Case 2 (no data, Le={le})"
elif len(bytes_list) > 5:
lc = bytes_list[4]
if len(bytes_list) == 5 + lc:
apdu_type = f"Case 3 (Lc={lc}, no response)"
elif len(bytes_list) == 6 + lc:
le = bytes_list[-1]
apdu_type = f"Case 4 (Lc={lc}, Le={le})"
else:
self.status.SetLabel("❌ Invalid APDU structure")
return
self.status.SetLabel(f"✅ Valid APDU: {apdu_type}")
except ValueError:
self.status.SetLabel("❌ Invalid hex characters")
except Exception as e:
self.status.SetLabel(f"❌ Validation error: {e}")
def on_clear(self, event):
"""Clear all inputs."""
self.apdu_ctrl.Clear()
self.len_ctrl.Clear()
self.status.SetLabel("Enter APDU command...")
class APDUInputApp(wx.App):
def OnInit(self):
frame = APDUInputFrame()
frame.Show()
return True
if __name__ == '__main__':
app = APDUInputApp()
app.MainLoop()The GUI components require wxPython to be installed:
pip install pyscard[Gui]
# or
pip install wxPython# wxPython-based types (require wxPython installation)
wx.Panel # Base panel class
wx.Frame # Base frame class
wx.App # Application class
wx.Validator # Input validator base class
# GUI event types
wx.Event # Base event class
wx.CommandEvent # Command event class
# Icon resource types
IconResource = str # Path to icon fileInstall with Tessl CLI
npx tessl i tessl/pypi-pyscard