""" Smart Card Command GUI Graphical interface for sending APDU commands to smart cards and escape commands to readers. REQUIRES: pyscard, tkinter For detailed requirements and installation, please refer to the README.md. WARNING: Escape commands are hardware-specific and may vary by reader model. """ import platform import sys # Import API functions from API import list_readers, parse_command_string, connect_to_reader, send_command, disconnect_reader try: from smartcard.util import toHexString from smartcard.scard import SCardReleaseContext except ImportError: print("smartcard imports failed - pyscard may not be properly installed") sys.exit(1) try: import tkinter as tk from tkinter import ttk, messagebox GUI_AVAILABLE = True except ImportError: GUI_AVAILABLE = False if __name__ == "__main__": error_msg = "tkinter library not found!\n\n" system = platform.system() if system == "Darwin": error_msg += "For macOS, tkinter should be included with Python.\n" error_msg += "If missing, install Python from python.org or use:\n" error_msg += " brew install python-tk\n" elif system == "Linux": error_msg += "For Ubuntu/Linux, install tkinter using:\n" error_msg += " sudo apt-get install python3-tk\n" else: error_msg += "For Windows, tkinter should be included with Python.\n" error_msg += "If missing, reinstall Python and ensure 'tcl/tk and IDLE' is selected.\n" print(error_msg) if sys.stdout.isatty(): input("Press Enter to exit...") sys.exit(1) if GUI_AVAILABLE: class SmartCardCommandUI: def __init__(self, root): self.root = root self.root.title("Send Command") try: self.root.iconbitmap("ACS_Small.ico") except Exception: pass self.platform = platform.system() self.is_macos = self.platform == "Darwin" self.is_linux = self.platform == "Linux" if self.is_linux: self.root.geometry("900x700") self.root.minsize(850, 600) elif self.is_macos: self.root.geometry("850x650") self.root.minsize(750, 550) else: self.root.geometry("850x650") self.root.minsize(750, 550) self.check_platform_requirements() self.create_widgets() self.refresh_readers() def create_widgets(self): # Reader selection frame reader_frame = ttk.LabelFrame(self.root, text="Smart Card Reader", padding=10) reader_frame.pack(fill="x", padx=10, pady=5) reader_inner = ttk.Frame(reader_frame) reader_inner.pack(fill="x") ttk.Label(reader_inner, text="Select Reader:").pack(side="left", padx=5) self.reader_var = tk.StringVar() self.reader_combo = ttk.Combobox(reader_inner, textvariable=self.reader_var, state="readonly") self.reader_combo.pack(side="left", fill="x", expand=True, padx=5) self.refresh_button = ttk.Button(reader_inner, text="Refresh", command=self.refresh_readers) self.refresh_button.pack(side="left", padx=5) # Command frame (contains both APDU and Escape commands) command_frame = ttk.LabelFrame(self.root, text="Commands", padding=10) command_frame.pack(fill="x", padx=10, pady=5) command_frame.columnconfigure(1, weight=1) command_frame.columnconfigure(2, weight=0) # APDU Command section ttk.Label(command_frame, text="Enter APDU (hex):").grid(row=0, column=0, padx=5, sticky="w") entry_width = 40 if self.is_linux else 35 self.apdu_entry = ttk.Entry(command_frame, width=entry_width) self.apdu_entry.grid(row=0, column=1, padx=5, sticky="ew") self.apdu_entry.insert(0, "FF CA 00 00 00") self.send_apdu_button = ttk.Button(command_frame, text="Send APDU", command=self.send_apdu) self.send_apdu_button.grid(row=0, column=2, padx=5, sticky="e") # Escape Command section (under APDU) ttk.Label(command_frame, text="Enter Escape Command (hex):").grid(row=1, column=0, padx=5, pady=(10,0), sticky="w") self.escape_entry = ttk.Entry(command_frame, width=entry_width) self.escape_entry.grid(row=1, column=1, padx=5, pady=(10,0), sticky="ew") self.escape_entry.insert(0, "E0 00 00 18 00") self.send_escape_button = ttk.Button(command_frame, text="Send Escape Command", command=self.send_escape_command) self.send_escape_button.grid(row=1, column=2, padx=5, pady=(10,0), sticky="e") # Response frame response_frame = ttk.LabelFrame(self.root, text="Response", padding=10) response_frame.pack(fill="both", expand=True, padx=10, pady=5) text_height = 14 if self.is_linux else 10 self.response_text = tk.Text(response_frame, height=text_height, wrap=tk.WORD) self.response_text.pack(fill="both", expand=True) def check_platform_requirements(self): if self.is_linux: try: import subprocess result = subprocess.run(['systemctl', 'is-active', 'pcscd'], capture_output=True, text=True, timeout=2) if result.returncode != 0: messagebox.showwarning( "PCSC Service", "pcscd service may not be running. Please ensure it is active.\n\n" "On Ubuntu/Linux, you might need to run:\n" " sudo systemctl start pcscd\n" " sudo systemctl enable pcscd\n" "If not installed:\n" " sudo apt-get install pcscd libpcsclite1" ) except (FileNotFoundError, subprocess.TimeoutExpired, Exception): pass def refresh_readers(self): reader_list = list_readers() self.reader_combo['values'] = [str(reader) for reader in reader_list] if len(reader_list) > 0: self.reader_combo.current(0) else: messagebox.showwarning("No Readers Found", "No smart card readers found! Please check connections and drivers.") def send_apdu(self): # Check if reader is selected if not self.reader_var.get(): messagebox.showerror("Error", "Please select a reader first!") return command_str = self.apdu_entry.get().strip() if not command_str: error_msg = "Please enter an APDU command." self.update_response_text(error_msg + "\n") return # Get selected reader reader_list = list_readers() selected_reader = None for reader in reader_list: if str(reader) == self.reader_var.get(): selected_reader = reader break if not selected_reader: messagebox.showerror("Error", "Selected reader not found!") return connection = None try: # Connect to reader in APDU mode connection, error_msg, _ = connect_to_reader(selected_reader) if not connection: help_msg = error_msg or "Connection failed. Please check reader and card." messagebox.showerror("Error", help_msg) return # Send APDU command success, response, sw1, sw2, error_msg = send_command( connection, command_str, mode='apdu' ) if success: self.update_response_text(f"Sent APDU: {toHexString(parse_command_string(command_str))}\n") self.update_response_text(f"Response Data: {toHexString(response)}\n") self.update_response_text(f"Status Words: {hex(sw1)[2:].zfill(2).upper()} {hex(sw2)[2:].zfill(2).upper()}\n") self.update_response_text("-" * 80 + "\n") else: self.update_response_text(f"Failed to send APDU: {error_msg}\n") messagebox.showerror("Error", f"Failed to send APDU: {error_msg}") except ValueError as e: error_msg = f"Invalid APDU format: {str(e)}" self.update_response_text(error_msg + "\n") messagebox.showerror("Error", error_msg) except Exception as e: error_msg = f"Error sending APDU: {str(e)}" self.update_response_text(error_msg + "\n") messagebox.showerror("Error", error_msg) finally: # Always disconnect disconnect_reader(connection) def send_escape_command(self): # Check if reader is selected if not self.reader_var.get(): messagebox.showerror("Error", "Please select a reader first!") return command_str = self.escape_entry.get().strip() if not command_str: error_msg = "Please enter an escape command." self.update_response_text(error_msg + "\n") return # Get selected reader reader_list = list_readers() selected_reader = None for reader in reader_list: if str(reader) == self.reader_var.get(): selected_reader = reader break if not selected_reader: messagebox.showerror("Error", "Selected reader not found!") return hcard = None hcontext = None try: # Connect to reader in escape mode (direct connection) hcard, error_msg, hcontext = connect_to_reader(selected_reader, mode='direct') if not hcard: help_msg = error_msg or "Connection failed. Please check reader." messagebox.showerror("Error", help_msg) return # Send escape command command_bytes = parse_command_string(command_str) self.update_response_text(f"Sending escape command: {toHexString(command_bytes)}\n") success, response, error_msg = send_command(hcard, command_bytes, mode='escape') if success: self.update_response_text("Escape command sent successfully\n") if response and len(response) > 0: response_str = toHexString(response[:50]) if len(response) > 50 else toHexString(response) self.update_response_text(f"Response ({len(response)} bytes): {response_str}\n") else: self.update_response_text("Response: (empty)\n") self.update_response_text("-" * 80 + "\n") else: self.update_response_text(f"Failed to send escape command: {error_msg}\n") messagebox.showerror("Error", f"Failed to send escape command: {error_msg}") except ValueError as e: error_msg = f"Invalid escape command format: {str(e)}" self.update_response_text(error_msg + "\n") messagebox.showerror("Error", error_msg) except Exception as e: error_msg = f"Error sending escape command: {str(e)}" self.update_response_text(error_msg + "\n") messagebox.showerror("Error", error_msg) finally: disconnect_reader(hcard, hcontext) def update_response_text(self, message): self.response_text.insert(tk.END, message) self.response_text.see(tk.END) def cleanup(self): # No persistent connections to clean up since we connect/disconnect in each operation pass if __name__ == "__main__": if GUI_AVAILABLE: root = tk.Tk() app = SmartCardCommandUI(root) def on_closing(): app.cleanup() root.destroy() root.protocol("WM_DELETE_WINDOW", on_closing) root.mainloop() else: print("GUI not available. Use the API functions programmatically:") print(" # Import from API.py for programmatic usage") print(" # from API import list_readers, connect_to_reader, send_command, disconnect_card")