first init

This commit is contained in:
ISMAIL MASSERAN
2026-09-01 10:12:31 +08:00
commit ca6e55f853
718 changed files with 204746 additions and 0 deletions
+306
View File
@@ -0,0 +1,306 @@
"""
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")