first init
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
"""
|
||||
Smart Card Communication Library
|
||||
|
||||
Unified cross-platform library for smart card communication.
|
||||
|
||||
REQUIRES: pyscard (pip install pyscard)
|
||||
See README.md for platform setup instructions.
|
||||
"""
|
||||
|
||||
import platform
|
||||
import sys
|
||||
|
||||
# Constants
|
||||
DEFAULT_IOCTL_CODE = 3500 # CCID escape command IOCTL code for smart card readers
|
||||
|
||||
# Try to import smartcard library with platform-specific error handling
|
||||
try:
|
||||
from smartcard.System import readers
|
||||
from smartcard.util import toHexString
|
||||
from smartcard.CardConnection import CardConnection
|
||||
from smartcard.scard import (
|
||||
SCARD_SCOPE_USER, SCARD_S_SUCCESS, SCARD_SHARE_DIRECT,
|
||||
SCARD_PROTOCOL_T0, SCARD_PROTOCOL_T1, SCARD_PROTOCOL_UNDEFINED,
|
||||
SCARD_LEAVE_CARD, SCARD_CTL_CODE, SCardEstablishContext,
|
||||
SCardConnect, SCardControl, SCardDisconnect, SCardReleaseContext,
|
||||
SCardGetErrorMessage
|
||||
)
|
||||
import smartcard.Exceptions
|
||||
except ImportError:
|
||||
error_msg = "pyscard library not found!\n\n"
|
||||
system = platform.system()
|
||||
if system == "Darwin": # macOS
|
||||
error_msg += "For macOS, install using:\n"
|
||||
error_msg += " pip install pyscard\n\n"
|
||||
error_msg += "Note: PCSC framework should be pre-installed on macOS."
|
||||
elif system == "Linux": # Ubuntu/Linux
|
||||
error_msg += "For Ubuntu/Linux, first install system dependencies:\n"
|
||||
error_msg += " sudo apt-get install pcscd libpcsclite1 libpcsclite-dev\n"
|
||||
error_msg += " sudo systemctl start pcscd\n"
|
||||
error_msg += " sudo systemctl enable pcscd\n\n"
|
||||
error_msg += "Then install Python package:\n"
|
||||
error_msg += " pip install pyscard"
|
||||
else: # Windows
|
||||
error_msg += "For Windows, install using:\n"
|
||||
error_msg += " pip install pyscard\n\n"
|
||||
error_msg += "Note: Windows Smart Card API should be available by default."
|
||||
|
||||
print(error_msg)
|
||||
if sys.stdout.isatty():
|
||||
input("Press Enter to exit...")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def list_readers():
|
||||
"""
|
||||
List all available smart card readers.
|
||||
|
||||
Returns:
|
||||
List of reader objects.
|
||||
"""
|
||||
try:
|
||||
reader_list = readers()
|
||||
return list(reader_list)
|
||||
except smartcard.Exceptions.NoReadersException:
|
||||
return []
|
||||
except Exception as e:
|
||||
print(f"Error listing readers: {str(e)}")
|
||||
return []
|
||||
|
||||
|
||||
def parse_command_string(hex_string):
|
||||
"""
|
||||
Parse a hexadecimal string into a list of bytes.
|
||||
|
||||
Args:
|
||||
hex_string: Hex string (spaces optional)
|
||||
|
||||
Returns:
|
||||
List of integers representing bytes
|
||||
|
||||
Raises:
|
||||
ValueError: If string contains invalid characters or has odd length
|
||||
"""
|
||||
hex_string = hex_string.replace(" ", "")
|
||||
if not all(c in '0123456789ABCDEFabcdef' for c in hex_string):
|
||||
raise ValueError("Invalid characters in hex string. Use hexadecimal values only.")
|
||||
if len(hex_string) % 2 != 0:
|
||||
raise ValueError("Hex string length must be even (each byte is 2 hex digits).")
|
||||
|
||||
return [int(hex_string[i:i + 2], 16) for i in range(0, len(hex_string), 2)]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# CONNECTION FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
def connect_to_reader(reader, mode='share', protocol='auto'):
|
||||
"""
|
||||
Connect to a smart card reader.
|
||||
|
||||
Args:
|
||||
reader: Reader object from list_readers() or reader name string.
|
||||
mode: 'share' for APDU communication (default), 'direct' for escape commands.
|
||||
protocol: 'auto' (default), 't0t1', or 'undefined'.
|
||||
|
||||
Returns:
|
||||
tuple: (connection, error_msg, context).
|
||||
"""
|
||||
if reader is None:
|
||||
return None, "Reader is None.", None
|
||||
|
||||
protocol_map = {
|
||||
't0t1': SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
|
||||
'undefined': SCARD_PROTOCOL_UNDEFINED,
|
||||
'auto': (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1) if platform.system() == "Darwin" else SCARD_PROTOCOL_UNDEFINED
|
||||
}
|
||||
protocol_value = protocol_map.get(protocol, protocol_map['auto'])
|
||||
|
||||
if mode == 'share':
|
||||
try:
|
||||
connection = reader.createConnection()
|
||||
connection.connect(CardConnection.T0_protocol | CardConnection.T1_protocol if protocol_value == (SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1) else None)
|
||||
return connection, None, None
|
||||
except smartcard.Exceptions.NoCardException:
|
||||
return None, "No card in reader. Please insert a smart card.", None
|
||||
except Exception as e:
|
||||
return None, f"Connection error: {str(e)}", None
|
||||
|
||||
elif mode == 'direct':
|
||||
hcontext = None
|
||||
connection_successful = False
|
||||
try:
|
||||
hresult, hcontext = SCardEstablishContext(SCARD_SCOPE_USER)
|
||||
if hresult != SCARD_S_SUCCESS:
|
||||
return None, f"Context setup failed: {SCardGetErrorMessage(hresult)}", None
|
||||
|
||||
reader_name = str(reader) if not isinstance(reader, str) else reader
|
||||
hresult, hcard, _ = SCardConnect(hcontext, reader_name, SCARD_SHARE_DIRECT, protocol_value)
|
||||
|
||||
if hresult == SCARD_S_SUCCESS:
|
||||
connection_successful = True
|
||||
return hcard, None, hcontext
|
||||
else:
|
||||
return None, f"Failed to connect: {SCardGetErrorMessage(hresult)}", None
|
||||
except Exception as e:
|
||||
return None, f"Exception connecting: {str(e)}", None
|
||||
finally:
|
||||
if hcontext and not connection_successful:
|
||||
try:
|
||||
SCardReleaseContext(hcontext)
|
||||
except:
|
||||
pass
|
||||
|
||||
else:
|
||||
return None, f"Invalid mode: {mode}. Use 'share' or 'direct'.", None
|
||||
|
||||
|
||||
def disconnect_reader(connection, hcontext=None):
|
||||
"""
|
||||
Disconnects from a smart card reader and releases context if applicable.
|
||||
|
||||
Args:
|
||||
connection: CardConnection object (APDU mode) or card handle (escape mode).
|
||||
hcontext: The established context handle for direct connections.
|
||||
"""
|
||||
try:
|
||||
if connection is None:
|
||||
return
|
||||
# pyscard returns CardConnectionDecorator, not CardConnection directly
|
||||
if hasattr(connection, 'disconnect'):
|
||||
connection.disconnect()
|
||||
else:
|
||||
SCardDisconnect(connection, SCARD_LEAVE_CARD)
|
||||
except Exception as e:
|
||||
print(f"Error disconnecting from reader: {str(e)}")
|
||||
finally:
|
||||
if hcontext:
|
||||
try:
|
||||
SCardReleaseContext(hcontext)
|
||||
except Exception as e:
|
||||
print(f"Error releasing context: {str(e)}")
|
||||
|
||||
# ============================================================================
|
||||
# COMMAND FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
def send_command(connection, command, mode='apdu', ioctl_code=DEFAULT_IOCTL_CODE):
|
||||
"""
|
||||
Send a command to a smart card device.
|
||||
|
||||
Args:
|
||||
connection: CardConnection object (APDU mode) or card handle (escape mode).
|
||||
command: Hex string (APDU mode) or byte array/list (escape mode).
|
||||
mode: 'apdu' for APDU commands, 'escape' for escape commands.
|
||||
ioctl_code: IOCTL code for escape commands (default: 3500 - CCID escape).
|
||||
|
||||
Returns:
|
||||
For APDU mode: (success, response, sw1, sw2, error_msg).
|
||||
For escape mode: (success, response, error_msg).
|
||||
"""
|
||||
if mode not in ('apdu', 'escape'):
|
||||
raise ValueError(f"Invalid mode: {mode}. Must be 'apdu' or 'escape'")
|
||||
|
||||
if connection is None:
|
||||
error_msg = "No connection. Please connect to a reader first."
|
||||
return (False, [], 0, 0, error_msg) if mode == 'apdu' else (False, [], error_msg)
|
||||
|
||||
try:
|
||||
if mode == 'apdu':
|
||||
apdu = parse_command_string(command)
|
||||
response, sw1, sw2 = connection.transmit(apdu)
|
||||
return True, response, sw1, sw2, None
|
||||
elif mode == 'escape':
|
||||
IOCTL_CCID_ESCAPE = SCARD_CTL_CODE(ioctl_code)
|
||||
hresult, response = SCardControl(connection, IOCTL_CCID_ESCAPE, command)
|
||||
return (True, response, None) if hresult == SCARD_S_SUCCESS else (False, response, SCardGetErrorMessage(hresult))
|
||||
else:
|
||||
error_msg = f"Invalid mode: {mode}. Use 'apdu' or 'escape'."
|
||||
return (False, [], 0, 0, error_msg) if mode == 'apdu' else (False, [], error_msg)
|
||||
except ValueError as e:
|
||||
error_msg = f"Invalid command format: {str(e)}"
|
||||
return (False, [], 0, 0, error_msg) if mode == 'apdu' else (False, [], error_msg)
|
||||
except Exception as e:
|
||||
error_msg = f"Transmission error: {str(e)}"
|
||||
return (False, [], 0, 0, error_msg) if mode == 'apdu' else (False, [], error_msg)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# UTILITY FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user