1015 lines
35 KiB
Python
Executable File
1015 lines
35 KiB
Python
Executable File
#! /usr/bin/python3
|
|
|
|
"""
|
|
# Display PC/SC functions arguments
|
|
# Copyright (C) 2011-2021 Ludovic Rousseau
|
|
"""
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import signal
|
|
import time
|
|
try:
|
|
# for Python3
|
|
from queue import Queue
|
|
except ImportError:
|
|
# for Python2
|
|
from Queue import Queue
|
|
from threading import Thread
|
|
from operator import attrgetter
|
|
|
|
|
|
def hexdump(data_buffer, width=16):
|
|
def quotechars(data_buffer):
|
|
return ''.join(['.', chr(c)][c > 31 and c < 127] for c in data_buffer)
|
|
|
|
result = []
|
|
offset = 0
|
|
while data_buffer:
|
|
line = data_buffer[:width]
|
|
data_buffer = data_buffer[width:]
|
|
hex_dump = " ".join("%02X" % c for c in line)
|
|
ascii_dump = quotechars(line)
|
|
if len(line) < width:
|
|
hex_dump += " " * (width - len(line))
|
|
result.append("%04X %s %s" % (offset, hex_dump, ascii_dump))
|
|
offset += width
|
|
return result
|
|
|
|
|
|
def _parse_rv(line):
|
|
""" parse the return value line """
|
|
if line == "":
|
|
raise Exception("Empty line (application exited?)")
|
|
|
|
(direction, sec, usec, function, code, rv) = line.split('|')
|
|
if direction != '<':
|
|
raise Exception("Wrong line:", line)
|
|
|
|
sec = int(sec)
|
|
usec = int(usec)
|
|
|
|
return (code, rv, sec, usec)
|
|
|
|
|
|
class SpyExit(Exception):
|
|
pass
|
|
|
|
class StatRecord(object):
|
|
""" Record to store statistics """
|
|
|
|
def __init__(self, name):
|
|
self.name = name
|
|
self.executions = list()
|
|
self.total_time = 0
|
|
self.occurences = 0
|
|
|
|
def __repr__(self):
|
|
return self.name + ": " + repr(self.executions)
|
|
|
|
|
|
class PCSCspy(object):
|
|
""" PC/SC spy """
|
|
|
|
color_red = "\x1b[01;31m"
|
|
color_green = "\x1b[32m"
|
|
color_blue = "\x1b[34m"
|
|
color_magenta = "\x1b[35m"
|
|
color_normal = "\x1b[0m"
|
|
|
|
def get_line(self):
|
|
line = self.queue.get()
|
|
if line == "EXIT":
|
|
raise SpyExit()
|
|
return line
|
|
|
|
def _log_rv(self):
|
|
""" log the return value """
|
|
line = self.get_line()
|
|
(code, rv, sec, usec) = _parse_rv(line)
|
|
delta_sec = sec - self.sec
|
|
delta_usec = usec - self.usec
|
|
if delta_usec < 0:
|
|
delta_sec -= 1
|
|
delta_usec += 1000000
|
|
if self.diffable:
|
|
time = " [??.??]"
|
|
else:
|
|
time = " [%d.%06d]" % (delta_sec, delta_usec)
|
|
self.execution_time = delta_sec + delta_usec / 1000000.
|
|
|
|
rvs = {
|
|
0x00000000: "SCARD_S_SUCCESS",
|
|
0x80100001: "SCARD_F_INTERNAL_ERROR",
|
|
0x80100002: "SCARD_E_CANCELLED",
|
|
0x80100003: "SCARD_E_INVALID_HANDLE",
|
|
0x80100004: "SCARD_E_INVALID_PARAMETER",
|
|
0x80100005: "SCARD_E_INVALID_TARGET",
|
|
0x80100006: "SCARD_E_NO_MEMORY",
|
|
0x80100007: "SCARD_F_WAITED_TOO_LONG",
|
|
0x80100008: "SCARD_E_INSUFFICIENT_BUFFER",
|
|
0x80100009: "SCARD_E_UNKNOWN_READER",
|
|
0x8010000A: "SCARD_E_TIMEOUT",
|
|
0x8010000B: "SCARD_E_SHARING_VIOLATION",
|
|
0x8010000C: "SCARD_E_NO_SMARTCARD",
|
|
0x8010000D: "SCARD_E_UNKNOWN_CARD",
|
|
0x8010000E: "SCARD_E_CANT_DISPOSE",
|
|
0x8010000F: "SCARD_E_PROTO_MISMATCH",
|
|
0x80100010: "SCARD_E_NOT_READY",
|
|
0x80100011: "SCARD_E_INVALID_VALUE",
|
|
0x80100012: "SCARD_E_SYSTEM_CANCELLED",
|
|
0x80100013: "SCARD_F_COMM_ERROR",
|
|
0x80100014: "SCARD_F_UNKNOWN_ERROR",
|
|
0x80100015: "SCARD_E_INVALID_ATR",
|
|
0x80100016: "SCARD_E_NOT_TRANSACTED",
|
|
0x80100017: "SCARD_E_READER_UNAVAILABLE",
|
|
0x80100018: "SCARD_P_SHUTDOWN",
|
|
0x80100019: "SCARD_E_PCI_TOO_SMALL",
|
|
0x8010001A: "SCARD_E_READER_UNSUPPORTED",
|
|
0x8010001B: "SCARD_E_DUPLICATE_READER",
|
|
0x8010001C: "SCARD_E_CARD_UNSUPPORTED",
|
|
0x8010001D: "SCARD_E_NO_SERVICE",
|
|
0x8010001E: "SCARD_E_SERVICE_STOPPED",
|
|
0x8010001F: "SCARD_E_UNSUPPORTED_FEATURE",
|
|
0x80100020: "SCARD_E_ICC_INSTALLATION",
|
|
0x80100021: "SCARD_E_ICC_CREATEORDER",
|
|
0x80100023: "SCARD_E_DIR_NOT_FOUND",
|
|
0x80100024: "SCARD_E_FILE_NOT_FOUND",
|
|
0x80100025: "SCARD_E_NO_DIR",
|
|
0x80100026: "SCARD_E_NO_FILE",
|
|
0x80100027: "SCARD_E_NO_ACCESS",
|
|
0x80100028: "SCARD_E_WRITE_TOO_MANY",
|
|
0x80100029: "SCARD_E_BAD_SEEK",
|
|
0x8010002A: "SCARD_E_INVALID_CHV",
|
|
0x8010002B: "SCARD_E_UNKNOWN_RES_MNG",
|
|
0x8010002C: "SCARD_E_NO_SUCH_CERTIFICATE",
|
|
0x8010002D: "SCARD_E_CERTIFICATE_UNAVAILABLE",
|
|
0x8010002E: "SCARD_E_NO_READERS_AVAILABLE",
|
|
0x8010002F: "SCARD_E_COMM_DATA_LOST",
|
|
0x80100030: "SCARD_E_NO_KEY_CONTAINER",
|
|
0x80100031: "SCARD_E_SERVER_TOO_BUSY",
|
|
0x80100065: "SCARD_W_UNSUPPORTED_CARD",
|
|
0x80100066: "SCARD_W_UNRESPONSIVE_CARD",
|
|
0x80100067: "SCARD_W_UNPOWERED_CARD",
|
|
0x80100068: "SCARD_W_RESET_CARD",
|
|
0x80100069: "SCARD_W_REMOVED_CARD",
|
|
0x8010006A: "SCARD_W_SECURITY_VIOLATION",
|
|
0x8010006B: "SCARD_W_WRONG_CHV",
|
|
0x8010006C: "SCARD_W_CHV_BLOCKED",
|
|
0x8010006D: "SCARD_W_EOF",
|
|
0x8010006E: "SCARD_W_CANCELLED_BY_USER",
|
|
0x8010006F: "SCARD_W_CARD_NOT_AUTHENTICATED",
|
|
}
|
|
rv_text = rvs[int(rv, 16)]
|
|
data = " => " + code + " (" + rv_text + " [" + rv + "]) "
|
|
if "0x00000000" != rv:
|
|
if self.color:
|
|
print(self.indent + PCSCspy.color_red + data +
|
|
PCSCspy.color_normal + time)
|
|
else:
|
|
print(self.indent + data + time)
|
|
else:
|
|
print(self.indent + data + time)
|
|
|
|
return rv_text
|
|
|
|
def log_in(self, line):
|
|
""" generic log for IN line """
|
|
if self.color:
|
|
print(self.indent + PCSCspy.color_green + " i " + line +
|
|
PCSCspy.color_normal)
|
|
else:
|
|
print(self.indent + " i " + line)
|
|
|
|
def log_out(self, line):
|
|
""" generic log for OUT line """
|
|
if self.color:
|
|
print(self.indent + PCSCspy.color_magenta + " o " + line +
|
|
PCSCspy.color_normal)
|
|
else:
|
|
print(self.indent + " o " + line)
|
|
|
|
def log_in_multi(self, lines, padding=""):
|
|
""" generic log for IN lines """
|
|
for line in lines:
|
|
self.log_in(padding + line)
|
|
|
|
def log_out_multi(self, lines, padding=""):
|
|
""" generic log for OUT lines """
|
|
for line in lines:
|
|
self.log_out(padding + line)
|
|
|
|
def log_in_hCard(self):
|
|
""" log hCard IN parameter """
|
|
hCard = self.get_line()
|
|
if self.diffable:
|
|
self.log_in("hCard: 0x????")
|
|
else:
|
|
self.log_in("hCard: %s" % hCard)
|
|
|
|
def log_in_scard_io_request(self):
|
|
""" log SCARD_IO_REQUEST IN parameter """
|
|
dwProtocol = self.get_line()
|
|
if self.diffable:
|
|
self.log_in("pioSendPci.dwProtocol: 0x????")
|
|
else:
|
|
self.log_in("pioSendPci.dwProtocol: %s" % dwProtocol)
|
|
|
|
cbPciLength = self.get_line()
|
|
if self.diffable:
|
|
self.log_in("pioSendPci.cbPciLength: 0x????")
|
|
else:
|
|
self.log_in("pioSendPci.cbPciLength: %s" % cbPciLength)
|
|
|
|
def log_in_hContext(self):
|
|
""" log hContext IN parameter """
|
|
hContext = self.get_line()
|
|
if self.diffable:
|
|
self.log_in("hContext: 0x????")
|
|
else:
|
|
self.log_in("hContext: %s" % hContext)
|
|
|
|
def log_in_disposition(self):
|
|
""" log dwDisposition IN parameter """
|
|
dwDisposition = self.get_line()
|
|
dispositions = {0: 'SCARD_LEAVE_CARD',
|
|
1: 'SCARD_RESET_CARD',
|
|
2: 'SCARD_UNPOWER_CARD',
|
|
3: 'SCARD_EJECT_CARD'}
|
|
try:
|
|
disposition = dispositions[int(dwDisposition, 16)]
|
|
except KeyError:
|
|
disposition = "UNKNOWN"
|
|
self.log_in("dwDisposition: %s (%s)" % (disposition,
|
|
dwDisposition))
|
|
|
|
def log_in_attrid(self):
|
|
""" log dwAttrId IN parameter """
|
|
dwAttrId = self.get_line()
|
|
attrids = {0x00010100: 'SCARD_ATTR_VENDOR_NAME',
|
|
0x00010102: 'SCARD_ATTR_VENDOR_IFD_VERSION',
|
|
0x00010103: 'SCARD_ATTR_VENDOR_IFD_SERIAL_NO',
|
|
0x0007A007: 'SCARD_ATTR_MAXINPUT',
|
|
0x00090300: 'SCARD_ATTR_ICC_PRESENCE',
|
|
0x00090301: 'SCARD_ATTR_ICC_INTERFACE_STATUS',
|
|
0x00090303: 'SCARD_ATTR_ATR_STRING',
|
|
0x7FFF0003: 'SCARD_ATTR_DEVICE_FRIENDLY_NAME_A',
|
|
0x7FFF0004: 'SCARD_ATTR_DEVICE_SYSTEM_NAME_A',
|
|
0x7FFF0005: 'SCARD_ATTR_DEVICE_FRIENDLY_NAME_W',
|
|
0x7FFF0006: 'SCARD_ATTR_DEVICE_SYSTEM_NAME_W'}
|
|
try:
|
|
attrid = attrids[int(dwAttrId, 16)]
|
|
except KeyError:
|
|
attrid = "UNKNOWN"
|
|
self.log_in("dwAttrId: %s (%s)" % (attrid, dwAttrId))
|
|
|
|
def log_in_dwShareMode(self):
|
|
""" log dwShareMode IN parameter """
|
|
dwShareMode = self.get_line()
|
|
sharemodes = {1: 'SCARD_SHARE_EXCLUSIVE',
|
|
2: 'SCARD_SHARE_SHARED',
|
|
3: 'SCARD_SHARE_DIRECT'}
|
|
try:
|
|
sharemode = sharemodes[int(dwShareMode, 16)]
|
|
except KeyError:
|
|
sharemode = "UNKNOWN"
|
|
self.log_in("dwShareMode: %s (%s)" % (sharemode, dwShareMode))
|
|
|
|
def log_in_dwPreferredProtocols(self):
|
|
""" log dwPreferredProtocols IN parameter """
|
|
dwPreferredProtocols = self.get_line()
|
|
PreferredProtocols = list()
|
|
protocol = int(dwPreferredProtocols, 16)
|
|
if protocol & 1:
|
|
PreferredProtocols.append("T=0")
|
|
if protocol & 2:
|
|
PreferredProtocols.append("T=1")
|
|
if protocol & 4:
|
|
PreferredProtocols.append("RAW")
|
|
if protocol & 8:
|
|
PreferredProtocols.append("T=15")
|
|
self.log_in("dwPreferredProtocols: %s (%s)" % (dwPreferredProtocols,
|
|
", ".join(PreferredProtocols)))
|
|
|
|
def log_out_scard_io_request(self):
|
|
""" log SCARD_IO_REQUEST OUT parameter """
|
|
dwProtocol = self.get_line()
|
|
if self.diffable:
|
|
self.log_out("pioRecvPci.dwProtocol: 0x????")
|
|
else:
|
|
self.log_out("pioRecvPci.dwProtocol: %s" % dwProtocol)
|
|
|
|
cbPciLength = self.get_line()
|
|
if self.diffable:
|
|
self.log_out("pioRecvPci.cbPciLength: 0x????")
|
|
else:
|
|
self.log_out("pioRecvPci.cbPciLength: %s" % cbPciLength)
|
|
|
|
def log_out_dwActiveProtocol(self):
|
|
""" log dwActiveProtocol OUT parameter """
|
|
dwActiveProtocol = self.get_line()
|
|
protocol = int(dwActiveProtocol, 16)
|
|
if protocol & 1:
|
|
protocol = "T=0"
|
|
elif protocol & 2:
|
|
protocol = "T=1"
|
|
elif protocol & 4:
|
|
protocol = "RAW"
|
|
elif protocol & 8:
|
|
protocol = "T=15"
|
|
else:
|
|
protocol = "UNKNOWN"
|
|
self.log_out("dwActiveProtocol: %s (%s)" % (protocol,
|
|
dwActiveProtocol))
|
|
|
|
def log_out_hContext(self):
|
|
""" log hContext OUT parameter """
|
|
hContext = self.get_line()
|
|
if self.diffable:
|
|
self.log_out("hContext: 0x????")
|
|
else:
|
|
self.log_out("hContext: %s" % hContext)
|
|
|
|
def _get_state(self, dwState):
|
|
""" parse dwCurrentState and dwEventState """
|
|
SCardStates = {0: 'SCARD_STATE_UNAWARE',
|
|
1: 'SCARD_STATE_IGNORE',
|
|
2: 'SCARD_STATE_CHANGED',
|
|
4: 'SCARD_STATE_UNKNOWN',
|
|
8: 'SCARD_STATE_UNAVAILABLE',
|
|
16: 'SCARD_STATE_EMPTY',
|
|
32: 'SCARD_STATE_PRESENT',
|
|
64: 'SCARD_STATE_ATRMATCH',
|
|
128: 'SCARD_STATE_EXCLUSIVE',
|
|
256: 'SCARD_STATE_INUSE',
|
|
512: 'SCARD_STATE_MUTE',
|
|
1024: 'SCARD_STATE_UNPOWERED'}
|
|
|
|
state = list()
|
|
for bit in SCardStates.keys():
|
|
if dwState & bit:
|
|
state.append(SCardStates[bit])
|
|
return ", ".join(state)
|
|
|
|
def log_dwCurrentState(self, log):
|
|
""" log dwCurrentState IN/OUT parameter """
|
|
dwCurrentState = self.get_line()
|
|
state = self._get_state(int(dwCurrentState, 16))
|
|
log(" dwCurrentState: %s (%s)" % (state, dwCurrentState))
|
|
|
|
def log_dwEventState(self, log):
|
|
""" log dwEventState IN/OUT parameter """
|
|
dwEventState = self.get_line()
|
|
state = self._get_state(int(dwEventState, 16))
|
|
log(" dwEventState: %s (%s)" % (state, dwEventState))
|
|
|
|
def log_dwControlCode(self):
|
|
""" log SCardControl() dwControlCode """
|
|
dwControlCode = self.get_line()
|
|
|
|
try:
|
|
code = self.ControlCodes[int(dwControlCode, 16)]
|
|
except KeyError:
|
|
code = "UNKNOWN"
|
|
self.log_in("dwControlCode: %s (%s)" % (code, dwControlCode))
|
|
|
|
return int(dwControlCode, 16)
|
|
|
|
def log_in2(self, header):
|
|
""" generic log IN parameter """
|
|
data = self.get_line()
|
|
if data.startswith("0x"):
|
|
decimal = int(data, 16)
|
|
self.log_in("%s %s (%d)" % (header, data, decimal))
|
|
else:
|
|
self.log_in("%s %s" % (header, data))
|
|
return data
|
|
|
|
def log_out2(self, header):
|
|
""" generic log OUT parameter """
|
|
data = self.get_line()
|
|
if data.startswith("0x"):
|
|
decimal = int(data, 16)
|
|
self.log_out("%s %s (%d)" % (header, data, decimal))
|
|
else:
|
|
self.log_out("%s %s" % (header, data))
|
|
return data
|
|
|
|
def log_out_n_str(self, size_name, field_name):
|
|
""" log multi-lines entries """
|
|
data = self.get_line()
|
|
self.log_out("%s %s" % (size_name, data))
|
|
size = int(data, 16)
|
|
data_read = 0
|
|
if 0 == size:
|
|
data = self.get_line()
|
|
self.log_out("%s %s" % (field_name, data))
|
|
else:
|
|
while data_read < size:
|
|
data = self.get_line()
|
|
self.log_out("%s %s" % (field_name, data))
|
|
if data == 'NULL':
|
|
break
|
|
data_read += len(data) + 1
|
|
|
|
def log_name(self, name):
|
|
""" log function name """
|
|
if self.color:
|
|
print(self.indent + PCSCspy.color_blue + name +
|
|
PCSCspy.color_normal)
|
|
else:
|
|
print(self.indent + name)
|
|
|
|
def _log_readers(self, readers, direction):
|
|
""" log SCARD_READERSTATE structure """
|
|
log = self.log_in2
|
|
raw_log = self.log_in
|
|
if (direction == "out"):
|
|
log = self.log_out2
|
|
raw_log = self.log_out
|
|
for index in range(readers):
|
|
log("szReader:")
|
|
self.log_dwCurrentState(raw_log)
|
|
self.log_dwEventState(raw_log)
|
|
log(" Atr length:")
|
|
log(" Atr:")
|
|
|
|
def log_buffer(self, field, direction):
|
|
log = self.log_in
|
|
log_multi = self.log_in_multi
|
|
if direction == "out":
|
|
log = self.log_out
|
|
log_multi = self.log_out_multi
|
|
|
|
hex_buffer = self.get_line()
|
|
log(field)
|
|
if hex_buffer == "NULL":
|
|
log(" NULL")
|
|
elif hex_buffer != "":
|
|
int_buffer = [int(x, 16) for x in hex_buffer.split(" ")]
|
|
formated_buffer = hexdump(int_buffer)
|
|
log_multi(formated_buffer, " ")
|
|
|
|
return hex_buffer
|
|
|
|
def _SCardEstablishContext(self):
|
|
""" SCardEstablishContext """
|
|
self.log_name("SCardEstablishContext")
|
|
dwScope = self.get_line()
|
|
scopes = {0: 'SCARD_SCOPE_USER',
|
|
1: 'SCARD_SCOPE_TERMINAL',
|
|
2: 'SCARD_SCOPE_SYSTEM'}
|
|
self.log_in("dwScope: %s (%s)" % (scopes[int(dwScope, 16)], dwScope))
|
|
self.log_out_hContext()
|
|
self._log_rv()
|
|
|
|
def _SCardIsValidContext(self):
|
|
""" SCardIsValidContext """
|
|
self.log_name("SCardIsValidContext")
|
|
self.log_in_hContext()
|
|
self._log_rv()
|
|
|
|
def _SCardReleaseContext(self):
|
|
""" SCardReleaseContext """
|
|
self.log_name("SCardReleaseContext")
|
|
self.log_in_hContext()
|
|
self._log_rv()
|
|
|
|
def _SCardListReaders(self):
|
|
""" SCardListReaders """
|
|
self.log_name("SCardListReaders")
|
|
self.log_in_hContext()
|
|
self.log_in2("mszGroups:")
|
|
self.log_out_n_str("pcchReaders:", "mszReaders:")
|
|
self._log_rv()
|
|
|
|
def _SCardListReaderGroups(self):
|
|
""" SCardListReaderGroups """
|
|
self.log_name("SCardListReaderGroups")
|
|
self.log_in_hContext()
|
|
self.log_in2("pcchGroups:")
|
|
self.log_out_n_str("pcchGroups:", "mszGroups:")
|
|
self._log_rv()
|
|
|
|
def _SCardGetStatusChange(self):
|
|
""" SCardGetStatusChange """
|
|
self.log_name("SCardGetStatusChange")
|
|
self.log_in_hContext()
|
|
self.log_in2("dwTimeout:")
|
|
readers = int(self.get_line(), 16)
|
|
self.log_in("cReaders: %d" % readers)
|
|
self._log_readers(readers, direction="in")
|
|
self._log_readers(readers, direction="out")
|
|
self._log_rv()
|
|
|
|
def _SCardFreeMemory(self):
|
|
""" SCardFreeMemory """
|
|
self.log_name("SCardFreeMemory")
|
|
self.log_in_hContext()
|
|
self.log_in2("pvMem:")
|
|
self._log_rv()
|
|
|
|
def _SCardConnect(self):
|
|
""" SCardConnect """
|
|
self.log_name("SCardConnect")
|
|
self.log_in_hContext()
|
|
self.log_in2("szReader")
|
|
self.log_in_dwShareMode()
|
|
self.log_in_dwPreferredProtocols()
|
|
self.log_in2("phCard")
|
|
self.log_in2("pdwActiveProtocol")
|
|
self.log_out2("phCard")
|
|
self.log_out_dwActiveProtocol()
|
|
self._log_rv()
|
|
|
|
def _SCardTransmit(self):
|
|
""" SCardTransmit """
|
|
self.log_name("SCardTransmit")
|
|
self.log_in_hCard()
|
|
self.log_in_scard_io_request()
|
|
self.log_in2("bSendLength")
|
|
self.log_buffer("bSendBuffer", "in")
|
|
self.log_out_scard_io_request()
|
|
self.log_out2("bRecvLength")
|
|
self.log_buffer("bRecvBuffer", "out")
|
|
self._log_rv()
|
|
|
|
def _SCardControl(self):
|
|
""" SCardControl """
|
|
self.log_name("SCarControl")
|
|
self.log_in_hCard()
|
|
dwControlCode = self.log_dwControlCode()
|
|
bSendLength = self.log_in2("bSendLength")
|
|
bSendBuffer = self.log_buffer("bSendBuffer", "in")
|
|
bRecvLength = self.log_out2("bRecvLength")
|
|
bRecvBuffer = self.log_buffer("bRecvBuffer", "out")
|
|
|
|
rv_text = self._log_rv()
|
|
|
|
# do not parse the received buffer in case of error
|
|
if rv_text != "SCARD_S_SUCCESS":
|
|
return
|
|
|
|
def hex2int(data, lengh):
|
|
return [int(x, 16) for x in data.split(" ")]
|
|
|
|
if dwControlCode == self.CM_IOCTL_GET_FEATURE_REQUEST:
|
|
print(" parsing CM_IOCTL_GET_FEATURE_REQUEST results:")
|
|
bRecvLength = int(bRecvLength, 16)
|
|
|
|
bRecvBuffer = hex2int(bRecvBuffer, bRecvLength)
|
|
|
|
# parse GET_FEATURE_REQUEST results
|
|
while bRecvBuffer:
|
|
tag = bRecvBuffer[0]
|
|
length = bRecvBuffer[1]
|
|
value = bRecvBuffer[2:2 + length]
|
|
value_int = value[3] + 256 * (value[2] + 256
|
|
* (value[1] + 256 * value[0]))
|
|
try:
|
|
self.ControlCodes[value_int] = self.features[tag]
|
|
self.__dict__[self.features[tag]] = value_int
|
|
except KeyError:
|
|
self.ControlCodes[value_int] = "UNKNOWN"
|
|
|
|
print(" Tag %s is 0x%X" % (self.ControlCodes[value_int],
|
|
value_int))
|
|
|
|
bRecvBuffer = bRecvBuffer[2 + length:]
|
|
|
|
elif dwControlCode == self.FEATURE_GET_TLV_PROPERTIES:
|
|
print(" parsing FEATURE_GET_TLV_PROPERTIES results:")
|
|
bRecvLength = int(bRecvLength, 16)
|
|
|
|
bRecvBuffer = hex2int(bRecvBuffer, bRecvLength)
|
|
|
|
tlv_properties = {
|
|
1: "PCSCv2_PART10_PROPERTY_wLcdLayout",
|
|
2: "PCSCv2_PART10_PROPERTY_bEntryValidationCondition",
|
|
3: "PCSCv2_PART10_PROPERTY_bTimeOut2",
|
|
4: "PCSCv2_PART10_PROPERTY_wLcdMaxCharacters",
|
|
5: "PCSCv2_PART10_PROPERTY_wLcdMaxLines",
|
|
6: "PCSCv2_PART10_PROPERTY_bMinPINSize",
|
|
7: "PCSCv2_PART10_PROPERTY_bMaxPINSize",
|
|
8: "PCSCv2_PART10_PROPERTY_sFirmwareID",
|
|
9: "PCSCv2_PART10_PROPERTY_bPPDUSupport"}
|
|
|
|
# parse GET_TLV_PROPERTIES results
|
|
while bRecvBuffer:
|
|
tag = bRecvBuffer[0]
|
|
length = bRecvBuffer[1]
|
|
value = bRecvBuffer[2:2 + length]
|
|
|
|
try:
|
|
tag_text = tlv_properties[tag]
|
|
except KeyError:
|
|
tag_text = "UNKNOWN"
|
|
|
|
print(" Tag:", tag_text)
|
|
print(" Length: ", length)
|
|
print(" Value:", value)
|
|
|
|
bRecvBuffer = bRecvBuffer[2 + length:]
|
|
|
|
elif dwControlCode == self.FEATURE_IFD_PIN_PROPERTIES:
|
|
print(" parsing FEATURE_IFD_PIN_PROPERTIES results:")
|
|
bRecvBuffer = hex2int(bRecvBuffer, int(bRecvLength, 16))
|
|
|
|
print(" wLcdLayout:", bRecvBuffer[0], bRecvBuffer[1])
|
|
print(" bEntryValidationCondition:", bRecvBuffer[2])
|
|
print(" bTimeOut2:", bRecvBuffer[3])
|
|
|
|
elif dwControlCode == self.FEATURE_VERIFY_PIN_DIRECT:
|
|
print(" parsing FEATURE_VERIFY_PIN_DIRECT:")
|
|
bSendBuffer = hex2int(bSendBuffer, int(bSendLength, 16))
|
|
|
|
print(" bTimerOut:", bSendBuffer[0])
|
|
print(" bTimerOut2:", bSendBuffer[1])
|
|
print(" bmFormatString:", bSendBuffer[2])
|
|
print(" bmPINBlockString:", bSendBuffer[3])
|
|
print(" bmPINLengthFormat:", bSendBuffer[4])
|
|
print(" wPINMaxExtraDigit: 0x%02X%02X" % (bSendBuffer[6],
|
|
bSendBuffer[5]))
|
|
print(" Min:", bSendBuffer[6])
|
|
print(" Max:", bSendBuffer[5])
|
|
print(" bEntryValidationCondition:", bSendBuffer[7])
|
|
print(" bNumberMessage:", bSendBuffer[8])
|
|
print(" wLangId: 0x%02X%02X" % (bSendBuffer[10],
|
|
bSendBuffer[9]))
|
|
print(" bMsgIndex:", bSendBuffer[11])
|
|
print(" bTeoPrologue:", bSendBuffer[12], bSendBuffer[13], \
|
|
bSendBuffer[14])
|
|
print(" ulDataLength:", bSendBuffer[15] + \
|
|
bSendBuffer[16] * 256 + bSendBuffer[17] * 2 ** 16 + \
|
|
bSendBuffer[18] * 2 ** 24)
|
|
print(" APDU:")
|
|
result = hexdump(bSendBuffer[19:])
|
|
for line in result:
|
|
print(" ", line)
|
|
|
|
def _SCardGetAttrib(self):
|
|
""" SCardGetAttrib """
|
|
self.log_name("SCardGetAttrib")
|
|
self.log_in_hCard()
|
|
self.log_in_attrid()
|
|
self.log_out2("bAttrLen")
|
|
self.log_buffer("bAttr", "out")
|
|
self._log_rv()
|
|
|
|
def _SCardSetAttrib(self):
|
|
""" SCardSetAttrib """
|
|
self.log_name("SCardSetAttrib")
|
|
self.log_in_hCard()
|
|
self.log_in_attrid()
|
|
self.log_in2("bAttrLen")
|
|
self.log_buffer("bAttr", "in")
|
|
self._log_rv()
|
|
|
|
def _SCardStatus(self):
|
|
""" SCardStatus """
|
|
self.log_name("SCardStatus")
|
|
self.log_in_hCard()
|
|
self.log_in2("pcchReaderLen")
|
|
self.log_in2("pcbAtrLen")
|
|
self.log_out2("cchReaderLen")
|
|
self.log_out2("mszReaderName")
|
|
self.log_out2("dwState")
|
|
self.log_out2("dwProtocol")
|
|
data = self.log_out2("bAtrLen")
|
|
if not data == "NULL":
|
|
self.log_out2("bAtr")
|
|
else:
|
|
self.log_out("bAtr")
|
|
self._log_rv()
|
|
|
|
def _SCardReconnect(self):
|
|
""" SCardReconnect """
|
|
self.log_name("SCardReconnect")
|
|
self.log_in_hCard()
|
|
self.log_in_dwShareMode()
|
|
self.log_in_dwPreferredProtocols()
|
|
self.log_in2("dwInitialization")
|
|
self.log_out_dwActiveProtocol()
|
|
self._log_rv()
|
|
|
|
def _SCardDisconnect(self):
|
|
"""" SCardDisconnect """
|
|
self.log_name("SCardDisconnect")
|
|
self.log_in_hCard()
|
|
self.log_in_disposition()
|
|
self._log_rv()
|
|
|
|
def _SCardBeginTransaction(self):
|
|
""" SCardBeginTransaction """
|
|
self.log_name("SCardBeginTransaction")
|
|
self.log_in_hCard()
|
|
self._log_rv()
|
|
|
|
def _SCardEndTransaction(self):
|
|
""" SCardEndTransaction """
|
|
self.log_name("SCardEndTransaction")
|
|
self.log_in_hCard()
|
|
self.log_in_disposition()
|
|
self._log_rv()
|
|
|
|
def _SCardCancel(self):
|
|
""" SCardCancel """
|
|
self.log_name("SCardCancel")
|
|
self.log_in_hCard()
|
|
self._log_rv()
|
|
|
|
def __init__(self, queue, stats, indent=0, color=True, diffable=False):
|
|
""" constructor """
|
|
|
|
# communication queue
|
|
self.queue = queue
|
|
|
|
self.color = color
|
|
self.diffable = diffable
|
|
self.stats = stats
|
|
self.indent = " " * (indent * 4)
|
|
if display_thread:
|
|
self.indent += " (t%d) " % indent
|
|
|
|
self.features = {0x01: "FEATURE_VERIFY_PIN_START",
|
|
0x02: "FEATURE_VERIFY_PIN_FINISH",
|
|
0x03: "FEATURE_MODIFY_PIN_START",
|
|
0x04: "FEATURE_MODIFY_PIN_FINISH",
|
|
0x05: "FEATURE_GET_KEY_PRESSED",
|
|
0x06: "FEATURE_VERIFY_PIN_DIRECT",
|
|
0x07: "FEATURE_MODIFY_PIN_DIRECT",
|
|
0x08: "FEATURE_MCT_READER_DIRECT",
|
|
0x09: "FEATURE_MCT_UNIVERSAL",
|
|
0x0A: "FEATURE_IFD_PIN_PROPERTIES",
|
|
0x0B: "FEATURE_ABORT",
|
|
0x0C: "FEATURE_SET_SPE_MESSAGE",
|
|
0x0D: "FEATURE_VERIFY_PIN_DIRECT_APP_ID",
|
|
0x0E: "FEATURE_MODIFY_PIN_DIRECT_APP_ID",
|
|
0x0F: "FEATURE_WRITE_DISPLAY",
|
|
0x10: "FEATURE_GET_KEY",
|
|
0x11: "FEATURE_IFD_DISPLAY_PROPERTIES",
|
|
0x12: "FEATURE_GET_TLV_PROPERTIES",
|
|
0x13: "FEATURE_CCID_ESC_COMMAND"}
|
|
|
|
def SCARD_CTL_CODE(code):
|
|
return 0x42000000 + code
|
|
|
|
self.CM_IOCTL_GET_FEATURE_REQUEST = SCARD_CTL_CODE(3400)
|
|
self.ControlCodes = {
|
|
SCARD_CTL_CODE(1): "IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE",
|
|
SCARD_CTL_CODE(3400): "CM_IOCTL_GET_FEATURE_REQUEST"
|
|
}
|
|
# dwControlCode not yet known
|
|
for key in self.features.keys():
|
|
self.__dict__[self.features[key]] = -1
|
|
|
|
def worker(self, *args):
|
|
line = self.queue.get()
|
|
while line != '':
|
|
# Enter function?
|
|
if line[0] != '>':
|
|
if line == 'EXIT':
|
|
return
|
|
else:
|
|
print("Garbage: ", line)
|
|
else:
|
|
try:
|
|
# dispatch
|
|
(direction, sec, usec, fct) = line.strip().split('|')
|
|
self.sec = int(sec)
|
|
self.usec = int(usec)
|
|
if fct == 'SCardEstablishContext':
|
|
self._SCardEstablishContext()
|
|
elif fct == 'SCardReleaseContext':
|
|
self._SCardReleaseContext()
|
|
elif fct == 'SCardIsValidContext':
|
|
self._SCardIsValidContext()
|
|
elif fct == 'SCardListReaderGroups':
|
|
self._SCardListReaderGroups()
|
|
elif fct == 'SCardFreeMemory':
|
|
self._SCardFreeMemory()
|
|
elif fct == 'SCardListReaders':
|
|
self._SCardListReaders()
|
|
elif fct == 'SCardGetStatusChange':
|
|
self._SCardGetStatusChange()
|
|
elif fct == 'SCardConnect':
|
|
self._SCardConnect()
|
|
elif fct == 'SCardTransmit':
|
|
self._SCardTransmit()
|
|
elif fct == 'SCardControl' or fct == 'SCardControl132':
|
|
self._SCardControl()
|
|
elif fct == 'SCardGetAttrib':
|
|
self._SCardGetAttrib()
|
|
elif fct == 'SCardSetAttrib':
|
|
self._SCardSetAttrib()
|
|
elif fct == 'SCardStatus':
|
|
self._SCardStatus()
|
|
elif fct == 'SCardReconnect':
|
|
self._SCardReconnect()
|
|
elif fct == 'SCardDisconnect':
|
|
self._SCardDisconnect()
|
|
elif fct == 'SCardBeginTransaction':
|
|
self._SCardBeginTransaction()
|
|
elif fct == 'SCardEndTransaction':
|
|
self._SCardEndTransaction()
|
|
elif fct == 'SCardCancel':
|
|
self._SCardCancel()
|
|
else:
|
|
print("Unknown function:", fct)
|
|
except SpyExit:
|
|
return
|
|
|
|
try:
|
|
record = self.stats[fct]
|
|
except KeyError:
|
|
record = self.stats[fct] = StatRecord(fct)
|
|
record.executions.append(self.execution_time)
|
|
|
|
line = self.queue.get()
|
|
|
|
|
|
class PCSCdemultiplexer(object):
|
|
def __init__(self, logfile=None, color=True, diffable=False):
|
|
""" constructor """
|
|
|
|
# use default fifo file?
|
|
if logfile is None:
|
|
logfile = os.path.expanduser('~/pcsc-spy')
|
|
|
|
# create the FIFO file
|
|
try:
|
|
os.mkfifo(logfile)
|
|
except OSError:
|
|
print("fifo %s already present. Reusing it." % logfile)
|
|
|
|
self.sec = self.usec = 0
|
|
|
|
self.fifo = logfile
|
|
self.filedesc2 = open(self.fifo, 'r')
|
|
|
|
self.queues = dict()
|
|
self.color = color
|
|
self.diffable = diffable
|
|
|
|
def __del__(self):
|
|
""" cleanup """
|
|
from stat import S_ISFIFO
|
|
file_stat = os.stat(self.fifo)
|
|
|
|
# remove the log fifo only if it is a FIFO and not a log file
|
|
if S_ISFIFO(file_stat.st_mode):
|
|
os.unlink(self.fifo)
|
|
|
|
def loop(self):
|
|
""" loop reading logs """
|
|
|
|
# for statistics
|
|
stats = dict()
|
|
|
|
threads = dict()
|
|
indent = 0
|
|
|
|
# dispatch
|
|
line = self.filedesc2.readline().strip()
|
|
|
|
(thread, tail) = line.split('@')
|
|
res = tail.strip().split('|')
|
|
# check the first line format
|
|
if res[0] != ">":
|
|
print("Wrong format!")
|
|
print("First line Should start with a '>' but got:")
|
|
print(tail)
|
|
return
|
|
(direction, sec, usec, fct) = res
|
|
|
|
start_time = int(sec) + int(usec) / 1000000.
|
|
|
|
lastest_result = ""
|
|
while line != '':
|
|
previous_thread = thread
|
|
(thread, tail) = line.split('@')
|
|
if "<" in tail:
|
|
lastest_result = tail
|
|
|
|
# in case the thread changes
|
|
if previous_thread != thread:
|
|
# schedule the other thread so it has time to empty its
|
|
# queue
|
|
time.sleep(.001)
|
|
|
|
try:
|
|
queue = self.queues[thread]
|
|
except KeyError:
|
|
queue = self.queues[thread] = Queue()
|
|
stats[thread] = dict()
|
|
# new worker
|
|
spy = PCSCspy(queue, stats[thread], indent=indent,
|
|
color=self.color, diffable=self.diffable)
|
|
threads[thread] = Thread(target=spy.worker)
|
|
threads[thread].start()
|
|
indent += 1
|
|
|
|
queue.put(tail)
|
|
|
|
line = self.filedesc2.readline().strip()
|
|
|
|
# tell the workers to exit
|
|
for thread in self.queues.keys():
|
|
self.queues[thread].put('EXIT')
|
|
|
|
# wait for all the workers to finish
|
|
for thread in threads:
|
|
threads[thread].join()
|
|
|
|
(code, rv, sec, usec) = _parse_rv(lastest_result)
|
|
end_time = sec + usec / 1000000.
|
|
total_time = end_time - start_time
|
|
|
|
# compute some statistics
|
|
thread_n = 1
|
|
for thread in stats:
|
|
stat = stats[thread]
|
|
for fct in stat:
|
|
record = stat[fct]
|
|
record.occurences = len(record.executions)
|
|
record.total_time = sum(record.executions)
|
|
|
|
records = [stat[fct] for fct in stat]
|
|
|
|
# display statistics sorted by total_time
|
|
print()
|
|
print("Thread %d/%d" % (thread_n, len(stats)))
|
|
print("Results sorted by total execution time")
|
|
print("total time: %f sec" % total_time)
|
|
for record in sorted(records, key=attrgetter('total_time'),
|
|
reverse=True):
|
|
print("%f sec (%3d calls) %5.2f%% %s" % (record.total_time,
|
|
record.occurences, record.total_time / total_time * 100.,
|
|
record.name))
|
|
|
|
thread_n += 1
|
|
|
|
|
|
def main(logfile=None, color=True, diffable=False):
|
|
""" main """
|
|
spy = PCSCdemultiplexer(logfile, color, diffable)
|
|
spy.loop()
|
|
|
|
|
|
def signal_handler(sig, frame):
|
|
print('Ctrl-C, exiting.')
|
|
os.kill(os.getpid(), signal.SIGQUIT)
|
|
|
|
|
|
def print_usage():
|
|
print("Usage: pcsc-spy [-n|--nocolor] [-d|--diffable] [-h|--help] [-v|--version] [-t|--thread]")
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
import getopt
|
|
|
|
logfile = None
|
|
try:
|
|
opts, args = getopt.getopt(sys.argv[1:], "ndhvt", ["nocolor",
|
|
"diffable", "help", "version", "thread"])
|
|
except getopt.GetoptError:
|
|
print_usage()
|
|
sys.exit(1)
|
|
|
|
color = True
|
|
diffable = False
|
|
display_thread = False
|
|
for o, a in opts:
|
|
if o == "-n" or o == "--nocolor":
|
|
color = False
|
|
if o == "-d" or o == "--diffable":
|
|
diffable = True
|
|
if o == "-h" or o == "--help":
|
|
print_usage()
|
|
sys.exit(1)
|
|
if o == "-v" or o == "--version":
|
|
print("pcsc-spy version 1.1")
|
|
print("Copyright (c) 2011-2021, Ludovic Rousseau <ludovic.rousseau@free.fr>")
|
|
print()
|
|
sys.exit(1)
|
|
if o == "-t" or o == "--thread":
|
|
display_thread = True
|
|
|
|
if len(args) > 0:
|
|
logfile = args[0]
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
main(logfile, color=color, diffable=diffable)
|