173 lines
6.1 KiB
Python
173 lines
6.1 KiB
Python
"""
|
|
Simple CLI Example - Smart Card Command Tool
|
|
|
|
A command-line interface demonstrating how to use the API.py library
|
|
for sending APDU and escape commands to smart card readers.
|
|
|
|
Usage:
|
|
python example_cli.py
|
|
"""
|
|
|
|
from API import list_readers, connect_to_reader, send_command, disconnect_reader, parse_command_string
|
|
from smartcard.util import toHexString
|
|
from smartcard.Exceptions import NoCardException
|
|
|
|
def list_available_readers():
|
|
"""List and display all available smart card readers."""
|
|
readers = list_readers()
|
|
|
|
if not readers:
|
|
print("No smart card readers found.")
|
|
return []
|
|
|
|
print("Available readers:")
|
|
for index, reader in enumerate(readers):
|
|
print(f"{index + 1}: {reader}")
|
|
|
|
return readers
|
|
|
|
def connect_to_reader_with_fallback(reader):
|
|
"""
|
|
Connect to reader with automatic fallback logic.
|
|
Tries standard mode (APDU) first, falls back to direct mode (escape) if no card.
|
|
|
|
Returns:
|
|
tuple: (connection, is_direct_mode, error_msg, context)
|
|
"""
|
|
# Try standard connection first (APDU mode - requires card)
|
|
connection, error_msg, _ = connect_to_reader(reader, mode='share')
|
|
|
|
if connection:
|
|
print("Connected in standard mode (APDU)")
|
|
return connection, False, None, None
|
|
|
|
# If connection failed due to no card, try direct mode (escape commands)
|
|
if error_msg and "No card" in error_msg:
|
|
print("No card detected, attempting direct mode...")
|
|
hcard, error_msg, hcontext = connect_to_reader(reader, mode='direct')
|
|
|
|
if hcard:
|
|
print("Connected using Direct Mode (Escape commands)")
|
|
return hcard, True, None, hcontext
|
|
else:
|
|
print(f"Direct Mode Failed: {error_msg}")
|
|
return None, False, error_msg, None
|
|
else:
|
|
print(f"Standard Connection Failed: {error_msg}")
|
|
return None, False, error_msg, None
|
|
|
|
def transmit_apdu(connection, command_hex):
|
|
"""Send an APDU command and display the response."""
|
|
try:
|
|
success, response, sw1, sw2, error_msg = send_command(
|
|
connection,
|
|
command_hex,
|
|
mode='apdu'
|
|
)
|
|
|
|
if success:
|
|
print(f"Response: {toHexString(response)}")
|
|
print(f"Status words: {sw1:02X} {sw2:02X}")
|
|
else:
|
|
print(f"Transmit Failed: {error_msg}")
|
|
except Exception as e:
|
|
print(f"Transmit Failed: {str(e)}")
|
|
|
|
def transmit_escape(connection, command_hex):
|
|
"""Send an escape command and display the response."""
|
|
try:
|
|
command_bytes = parse_command_string(command_hex)
|
|
success, response, error_msg = send_command(
|
|
connection,
|
|
command_bytes,
|
|
mode='escape'
|
|
)
|
|
|
|
if success:
|
|
if response and len(response) > 0:
|
|
# Show first 50 bytes if response is long
|
|
if len(response) > 50:
|
|
response_str = toHexString(response[:50])
|
|
print(f"Response ({len(response)} bytes): {response_str}...")
|
|
else:
|
|
print(f"Response ({len(response)} bytes): {toHexString(response)}")
|
|
else:
|
|
print("Response: (empty)")
|
|
else:
|
|
print(f"Control Failed: {error_msg}")
|
|
except Exception as e:
|
|
print(f"Control Failed: {str(e)}")
|
|
|
|
def main():
|
|
"""Main interactive loop following apdu.py logic."""
|
|
print("=" * 80)
|
|
print("Smart Card Command Tool - CLI Example")
|
|
print("=" * 80)
|
|
print()
|
|
|
|
while True:
|
|
# List available readers
|
|
pcsc_readers = list_available_readers()
|
|
if not pcsc_readers:
|
|
print("No readers available, exiting...")
|
|
break
|
|
|
|
# Prompt user to select reader or quit
|
|
try:
|
|
user_input = input("\nSelect the reader to connect or press 'q' to exit: ").strip()
|
|
except KeyboardInterrupt:
|
|
print("\n\nExiting...")
|
|
break
|
|
|
|
if user_input.lower() == 'q':
|
|
break
|
|
|
|
if not user_input:
|
|
print("Please enter a number or 'q'")
|
|
continue
|
|
|
|
try:
|
|
reader_index = int(user_input) - 1
|
|
if 0 <= reader_index < len(pcsc_readers):
|
|
selected_reader = pcsc_readers[reader_index]
|
|
|
|
# Connect with automatic fallback
|
|
connection, is_direct, error_msg, hcontext = connect_to_reader_with_fallback(selected_reader)
|
|
|
|
if connection:
|
|
try:
|
|
if is_direct:
|
|
# Direct mode - escape commands
|
|
data = input("Enter the control data to transmit (in hex, e.g., 'E0 00 00 18 00'): ").strip()
|
|
if data:
|
|
transmit_escape(connection, data)
|
|
else:
|
|
# Standard mode - APDU commands
|
|
data = input("Enter the APDU to transmit (in hex, e.g., 'FF CA 00 00 00'): ").strip()
|
|
if data:
|
|
transmit_apdu(connection, data)
|
|
except KeyboardInterrupt:
|
|
print("\nCommand cancelled.")
|
|
except Exception as e:
|
|
print(f"Error: {str(e)}")
|
|
finally:
|
|
# Disconnect based on mode
|
|
if is_direct:
|
|
disconnect_reader(connection, hcontext)
|
|
else:
|
|
disconnect_reader(connection)
|
|
else:
|
|
print("Connection failed")
|
|
else:
|
|
print("Invalid reader index")
|
|
except ValueError:
|
|
print("Please enter a valid number or 'q'")
|
|
except KeyboardInterrupt:
|
|
print("\n\nExiting...")
|
|
break
|
|
|
|
print("\nProgram completed.")
|
|
|
|
if __name__ == "__main__":
|
|
main()
|