1#!/usr/local/bin/python3.8 2 3""" 4# Display PC/SC functions arguments 5# Copyright (C) 2011-2021 Ludovic Rousseau 6""" 7# 8# This program is free software: you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation, either version 3 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program. If not, see <http://www.gnu.org/licenses/>. 20 21import os 22import signal 23import time 24try: 25 # for Python3 26 from queue import Queue 27except ImportError: 28 # for Python2 29 from Queue import Queue 30from threading import Thread 31from operator import attrgetter 32 33 34def hexdump(data_buffer, width=16): 35 def quotechars(data_buffer): 36 return ''.join(['.', chr(c)][c > 31 and c < 127] for c in data_buffer) 37 38 result = [] 39 offset = 0 40 while data_buffer: 41 line = data_buffer[:width] 42 data_buffer = data_buffer[width:] 43 hex_dump = " ".join("%02X" % c for c in line) 44 ascii_dump = quotechars(line) 45 if len(line) < width: 46 hex_dump += " " * (width - len(line)) 47 result.append("%04X %s %s" % (offset, hex_dump, ascii_dump)) 48 offset += width 49 return result 50 51 52def _parse_rv(line): 53 """ parse the return value line """ 54 if line == "": 55 raise Exception("Empty line (application exited?)") 56 57 (direction, sec, usec, function, code, rv) = line.split('|') 58 if direction != '<': 59 raise Exception("Wrong line:", line) 60 61 sec = int(sec) 62 usec = int(usec) 63 64 return (code, rv, sec, usec) 65 66 67class SpyExit(Exception): 68 pass 69 70class StatRecord(object): 71 """ Record to store statistics """ 72 73 def __init__(self, name): 74 self.name = name 75 self.executions = list() 76 self.total_time = 0 77 self.occurences = 0 78 79 def __repr__(self): 80 return self.name + ": " + repr(self.executions) 81 82 83class PCSCspy(object): 84 """ PC/SC spy """ 85 86 color_red = "\x1b[01;31m" 87 color_green = "\x1b[32m" 88 color_blue = "\x1b[34m" 89 color_magenta = "\x1b[35m" 90 color_normal = "\x1b[0m" 91 92 def get_line(self): 93 line = self.queue.get() 94 if line == "EXIT": 95 raise SpyExit() 96 return line 97 98 def _log_rv(self): 99 """ log the return value """ 100 line = self.get_line() 101 (code, rv, sec, usec) = _parse_rv(line) 102 delta_sec = sec - self.sec 103 delta_usec = usec - self.usec 104 if delta_usec < 0: 105 delta_sec -= 1 106 delta_usec += 1000000 107 if self.diffable: 108 time = " [??.??]" 109 else: 110 time = " [%d.%06d]" % (delta_sec, delta_usec) 111 self.execution_time = delta_sec + delta_usec / 1000000. 112 113 rvs = { 114 0x00000000: "SCARD_S_SUCCESS", 115 0x80100001: "SCARD_F_INTERNAL_ERROR", 116 0x80100002: "SCARD_E_CANCELLED", 117 0x80100003: "SCARD_E_INVALID_HANDLE", 118 0x80100004: "SCARD_E_INVALID_PARAMETER", 119 0x80100005: "SCARD_E_INVALID_TARGET", 120 0x80100006: "SCARD_E_NO_MEMORY", 121 0x80100007: "SCARD_F_WAITED_TOO_LONG", 122 0x80100008: "SCARD_E_INSUFFICIENT_BUFFER", 123 0x80100009: "SCARD_E_UNKNOWN_READER", 124 0x8010000A: "SCARD_E_TIMEOUT", 125 0x8010000B: "SCARD_E_SHARING_VIOLATION", 126 0x8010000C: "SCARD_E_NO_SMARTCARD", 127 0x8010000D: "SCARD_E_UNKNOWN_CARD", 128 0x8010000E: "SCARD_E_CANT_DISPOSE", 129 0x8010000F: "SCARD_E_PROTO_MISMATCH", 130 0x80100010: "SCARD_E_NOT_READY", 131 0x80100011: "SCARD_E_INVALID_VALUE", 132 0x80100012: "SCARD_E_SYSTEM_CANCELLED", 133 0x80100013: "SCARD_F_COMM_ERROR", 134 0x80100014: "SCARD_F_UNKNOWN_ERROR", 135 0x80100015: "SCARD_E_INVALID_ATR", 136 0x80100016: "SCARD_E_NOT_TRANSACTED", 137 0x80100017: "SCARD_E_READER_UNAVAILABLE", 138 0x80100018: "SCARD_P_SHUTDOWN", 139 0x80100019: "SCARD_E_PCI_TOO_SMALL", 140 0x8010001A: "SCARD_E_READER_UNSUPPORTED", 141 0x8010001B: "SCARD_E_DUPLICATE_READER", 142 0x8010001C: "SCARD_E_CARD_UNSUPPORTED", 143 0x8010001D: "SCARD_E_NO_SERVICE", 144 0x8010001E: "SCARD_E_SERVICE_STOPPED", 145 0x8010001F: "SCARD_E_UNSUPPORTED_FEATURE", 146 0x80100020: "SCARD_E_ICC_INSTALLATION", 147 0x80100021: "SCARD_E_ICC_CREATEORDER", 148 0x80100023: "SCARD_E_DIR_NOT_FOUND", 149 0x80100024: "SCARD_E_FILE_NOT_FOUND", 150 0x80100025: "SCARD_E_NO_DIR", 151 0x80100026: "SCARD_E_NO_FILE", 152 0x80100027: "SCARD_E_NO_ACCESS", 153 0x80100028: "SCARD_E_WRITE_TOO_MANY", 154 0x80100029: "SCARD_E_BAD_SEEK", 155 0x8010002A: "SCARD_E_INVALID_CHV", 156 0x8010002B: "SCARD_E_UNKNOWN_RES_MNG", 157 0x8010002C: "SCARD_E_NO_SUCH_CERTIFICATE", 158 0x8010002D: "SCARD_E_CERTIFICATE_UNAVAILABLE", 159 0x8010002E: "SCARD_E_NO_READERS_AVAILABLE", 160 0x8010002F: "SCARD_E_COMM_DATA_LOST", 161 0x80100030: "SCARD_E_NO_KEY_CONTAINER", 162 0x80100031: "SCARD_E_SERVER_TOO_BUSY", 163 0x80100065: "SCARD_W_UNSUPPORTED_CARD", 164 0x80100066: "SCARD_W_UNRESPONSIVE_CARD", 165 0x80100067: "SCARD_W_UNPOWERED_CARD", 166 0x80100068: "SCARD_W_RESET_CARD", 167 0x80100069: "SCARD_W_REMOVED_CARD", 168 0x8010006A: "SCARD_W_SECURITY_VIOLATION", 169 0x8010006B: "SCARD_W_WRONG_CHV", 170 0x8010006C: "SCARD_W_CHV_BLOCKED", 171 0x8010006D: "SCARD_W_EOF", 172 0x8010006E: "SCARD_W_CANCELLED_BY_USER", 173 0x8010006F: "SCARD_W_CARD_NOT_AUTHENTICATED", 174 } 175 rv_text = rvs[int(rv, 16)] 176 data = " => " + code + " (" + rv_text + " [" + rv + "]) " 177 if "0x00000000" != rv: 178 if self.color: 179 print(self.indent + PCSCspy.color_red + data + 180 PCSCspy.color_normal + time) 181 else: 182 print(self.indent + data + time) 183 else: 184 print(self.indent + data + time) 185 186 return rv_text 187 188 def log_in(self, line): 189 """ generic log for IN line """ 190 if self.color: 191 print(self.indent + PCSCspy.color_green + " i " + line + 192 PCSCspy.color_normal) 193 else: 194 print(self.indent + " i " + line) 195 196 def log_out(self, line): 197 """ generic log for OUT line """ 198 if self.color: 199 print(self.indent + PCSCspy.color_magenta + " o " + line + 200 PCSCspy.color_normal) 201 else: 202 print(self.indent + " o " + line) 203 204 def log_in_multi(self, lines, padding=""): 205 """ generic log for IN lines """ 206 for line in lines: 207 self.log_in(padding + line) 208 209 def log_out_multi(self, lines, padding=""): 210 """ generic log for OUT lines """ 211 for line in lines: 212 self.log_out(padding + line) 213 214 def log_in_hCard(self): 215 """ log hCard IN parameter """ 216 hCard = self.get_line() 217 if self.diffable: 218 self.log_in("hCard: 0x????") 219 else: 220 self.log_in("hCard: %s" % hCard) 221 222 def log_in_hContext(self): 223 """ log hContext IN parameter """ 224 hContext = self.get_line() 225 if self.diffable: 226 self.log_in("hContext: 0x????") 227 else: 228 self.log_in("hContext: %s" % hContext) 229 230 def log_in_disposition(self): 231 """ log dwDisposition IN parameter """ 232 dwDisposition = self.get_line() 233 dispositions = {0: 'SCARD_LEAVE_CARD', 234 1: 'SCARD_RESET_CARD', 235 2: 'SCARD_UNPOWER_CARD', 236 3: 'SCARD_EJECT_CARD'} 237 try: 238 disposition = dispositions[int(dwDisposition, 16)] 239 except KeyError: 240 disposition = "UNKNOWN" 241 self.log_in("dwDisposition: %s (%s)" % (disposition, 242 dwDisposition)) 243 244 def log_in_attrid(self): 245 """ log dwAttrId IN parameter """ 246 dwAttrId = self.get_line() 247 attrids = {0x00010100: 'SCARD_ATTR_VENDOR_NAME', 248 0x00010102: 'SCARD_ATTR_VENDOR_IFD_VERSION', 249 0x00010103: 'SCARD_ATTR_VENDOR_IFD_SERIAL_NO', 250 0x0007A007: 'SCARD_ATTR_MAXINPUT', 251 0x00090300: 'SCARD_ATTR_ICC_PRESENCE', 252 0x00090301: 'SCARD_ATTR_ICC_INTERFACE_STATUS', 253 0x00090303: 'SCARD_ATTR_ATR_STRING', 254 0x7FFF0003: 'SCARD_ATTR_DEVICE_FRIENDLY_NAME_A', 255 0x7FFF0004: 'SCARD_ATTR_DEVICE_SYSTEM_NAME_A', 256 0x7FFF0005: 'SCARD_ATTR_DEVICE_FRIENDLY_NAME_W', 257 0x7FFF0006: 'SCARD_ATTR_DEVICE_SYSTEM_NAME_W'} 258 try: 259 attrid = attrids[int(dwAttrId, 16)] 260 except KeyError: 261 attrid = "UNKNOWN" 262 self.log_in("dwAttrId: %s (%s)" % (attrid, dwAttrId)) 263 264 def log_in_dwShareMode(self): 265 """ log dwShareMode IN parameter """ 266 dwShareMode = self.get_line() 267 sharemodes = {1: 'SCARD_SHARE_EXCLUSIVE', 268 2: 'SCARD_SHARE_SHARED', 269 3: 'SCARD_SHARE_DIRECT'} 270 try: 271 sharemode = sharemodes[int(dwShareMode, 16)] 272 except KeyError: 273 sharemode = "UNKNOWN" 274 self.log_in("dwShareMode: %s (%s)" % (sharemode, dwShareMode)) 275 276 def log_in_dwPreferredProtocols(self): 277 """ log dwPreferredProtocols IN parameter """ 278 dwPreferredProtocols = self.get_line() 279 PreferredProtocols = list() 280 protocol = int(dwPreferredProtocols, 16) 281 if protocol & 1: 282 PreferredProtocols.append("T=0") 283 if protocol & 2: 284 PreferredProtocols.append("T=1") 285 if protocol & 4: 286 PreferredProtocols.append("RAW") 287 if protocol & 8: 288 PreferredProtocols.append("T=15") 289 self.log_in("dwPreferredProtocols: %s (%s)" % (dwPreferredProtocols, 290 ", ".join(PreferredProtocols))) 291 292 def log_out_dwActiveProtocol(self): 293 """ log dwActiveProtocol OUT parameter """ 294 dwActiveProtocol = self.get_line() 295 protocol = int(dwActiveProtocol, 16) 296 if protocol & 1: 297 protocol = "T=0" 298 elif protocol & 2: 299 protocol = "T=1" 300 elif protocol & 4: 301 protocol = "RAW" 302 elif protocol & 8: 303 protocol = "T=15" 304 else: 305 protocol = "UNKNOWN" 306 self.log_out("dwActiveProtocol: %s (%s)" % (protocol, 307 dwActiveProtocol)) 308 309 def log_out_hContext(self): 310 """ log hContext OUT parameter """ 311 hContext = self.get_line() 312 if self.diffable: 313 self.log_out("hContext: 0x????") 314 else: 315 self.log_out("hContext: %s" % hContext) 316 317 def _get_state(self, dwState): 318 """ parse dwCurrentState and dwEventState """ 319 SCardStates = {0: 'SCARD_STATE_UNAWARE', 320 1: 'SCARD_STATE_IGNORE', 321 2: 'SCARD_STATE_CHANGED', 322 4: 'SCARD_STATE_UNKNOWN', 323 8: 'SCARD_STATE_UNAVAILABLE', 324 16: 'SCARD_STATE_EMPTY', 325 32: 'SCARD_STATE_PRESENT', 326 64: 'SCARD_STATE_ATRMATCH', 327 128: 'SCARD_STATE_EXCLUSIVE', 328 256: 'SCARD_STATE_INUSE', 329 512: 'SCARD_STATE_MUTE', 330 1024: 'SCARD_STATE_UNPOWERED'} 331 332 state = list() 333 for bit in SCardStates.keys(): 334 if dwState & bit: 335 state.append(SCardStates[bit]) 336 return ", ".join(state) 337 338 def log_dwCurrentState(self, log): 339 """ log dwCurrentState IN/OUT parameter """ 340 dwCurrentState = self.get_line() 341 state = self._get_state(int(dwCurrentState, 16)) 342 log(" dwCurrentState: %s (%s)" % (state, dwCurrentState)) 343 344 def log_dwEventState(self, log): 345 """ log dwEventState IN/OUT parameter """ 346 dwEventState = self.get_line() 347 state = self._get_state(int(dwEventState, 16)) 348 log(" dwEventState: %s (%s)" % (state, dwEventState)) 349 350 def log_dwControlCode(self): 351 """ log SCardControl() dwControlCode """ 352 dwControlCode = self.get_line() 353 354 try: 355 code = self.ControlCodes[int(dwControlCode, 16)] 356 except KeyError: 357 code = "UNKNOWN" 358 self.log_in("dwControlCode: %s (%s)" % (code, dwControlCode)) 359 360 return int(dwControlCode, 16) 361 362 def log_in2(self, header): 363 """ generic log IN parameter """ 364 data = self.get_line() 365 if data.startswith("0x"): 366 decimal = int(data, 16) 367 self.log_in("%s %s (%d)" % (header, data, decimal)) 368 else: 369 self.log_in("%s %s" % (header, data)) 370 return data 371 372 def log_out2(self, header): 373 """ generic log OUT parameter """ 374 data = self.get_line() 375 if data.startswith("0x"): 376 decimal = int(data, 16) 377 self.log_out("%s %s (%d)" % (header, data, decimal)) 378 else: 379 self.log_out("%s %s" % (header, data)) 380 return data 381 382 def log_out_n_str(self, size_name, field_name): 383 """ log multi-lines entries """ 384 data = self.get_line() 385 self.log_out("%s %s" % (size_name, data)) 386 size = int(data, 16) 387 data_read = 0 388 if 0 == size: 389 data = self.get_line() 390 self.log_out("%s %s" % (field_name, data)) 391 else: 392 while data_read < size: 393 data = self.get_line() 394 self.log_out("%s %s" % (field_name, data)) 395 if data == 'NULL': 396 break 397 data_read += len(data) + 1 398 399 def log_name(self, name): 400 """ log function name """ 401 if self.color: 402 print(self.indent + PCSCspy.color_blue + name + 403 PCSCspy.color_normal) 404 else: 405 print(self.indent + name) 406 407 def _log_readers(self, readers, direction): 408 """ log SCARD_READERSTATE structure """ 409 log = self.log_in2 410 raw_log = self.log_in 411 if (direction == "out"): 412 log = self.log_out2 413 raw_log = self.log_out 414 for index in range(readers): 415 log("szReader:") 416 self.log_dwCurrentState(raw_log) 417 self.log_dwEventState(raw_log) 418 log(" Atr length:") 419 log(" Atr:") 420 421 def log_buffer(self, field, direction): 422 log = self.log_in 423 log_multi = self.log_in_multi 424 if direction == "out": 425 log = self.log_out 426 log_multi = self.log_out_multi 427 428 hex_buffer = self.get_line() 429 log(field) 430 if hex_buffer == "NULL": 431 log(" NULL") 432 elif hex_buffer != "": 433 int_buffer = [int(x, 16) for x in hex_buffer.split(" ")] 434 formated_buffer = hexdump(int_buffer) 435 log_multi(formated_buffer, " ") 436 437 return hex_buffer 438 439 def _SCardEstablishContext(self): 440 """ SCardEstablishContext """ 441 self.log_name("SCardEstablishContext") 442 dwScope = self.get_line() 443 scopes = {0: 'SCARD_SCOPE_USER', 444 1: 'SCARD_SCOPE_TERMINAL', 445 2: 'SCARD_SCOPE_SYSTEM'} 446 self.log_in("dwScope: %s (%s)" % (scopes[int(dwScope, 16)], dwScope)) 447 self.log_out_hContext() 448 self._log_rv() 449 450 def _SCardIsValidContext(self): 451 """ SCardIsValidContext """ 452 self.log_name("SCardIsValidContext") 453 self.log_in_hContext() 454 self._log_rv() 455 456 def _SCardReleaseContext(self): 457 """ SCardReleaseContext """ 458 self.log_name("SCardReleaseContext") 459 self.log_in_hContext() 460 self._log_rv() 461 462 def _SCardListReaders(self): 463 """ SCardListReaders """ 464 self.log_name("SCardListReaders") 465 self.log_in_hContext() 466 self.log_in2("mszGroups:") 467 self.log_out_n_str("pcchReaders:", "mszReaders:") 468 self._log_rv() 469 470 def _SCardListReaderGroups(self): 471 """ SCardListReaderGroups """ 472 self.log_name("SCardListReaderGroups") 473 self.log_in_hContext() 474 self.log_in2("pcchGroups:") 475 self.log_out_n_str("pcchGroups:", "mszGroups:") 476 self._log_rv() 477 478 def _SCardGetStatusChange(self): 479 """ SCardGetStatusChange """ 480 self.log_name("SCardGetStatusChange") 481 self.log_in_hContext() 482 self.log_in2("dwTimeout:") 483 readers = int(self.get_line(), 16) 484 self.log_in("cReaders: %d" % readers) 485 self._log_readers(readers, direction="in") 486 self._log_readers(readers, direction="out") 487 self._log_rv() 488 489 def _SCardFreeMemory(self): 490 """ SCardFreeMemory """ 491 self.log_name("SCardFreeMemory") 492 self.log_in_hContext() 493 self.log_in2("pvMem:") 494 self._log_rv() 495 496 def _SCardConnect(self): 497 """ SCardConnect """ 498 self.log_name("SCardConnect") 499 self.log_in_hContext() 500 self.log_in2("szReader") 501 self.log_in_dwShareMode() 502 self.log_in_dwPreferredProtocols() 503 self.log_in2("phCard") 504 self.log_in2("pdwActiveProtocol") 505 self.log_out2("phCard") 506 self.log_out_dwActiveProtocol() 507 self._log_rv() 508 509 def _SCardTransmit(self): 510 """ SCardTransmit """ 511 self.log_name("SCardTransmit") 512 self.log_in_hCard() 513 self.log_in2("bSendLength") 514 self.log_buffer("bSendBuffer", "in") 515 self.log_out2("bRecvLength") 516 self.log_buffer("bRecvBuffer", "out") 517 self._log_rv() 518 519 def _SCardControl(self): 520 """ SCardControl """ 521 self.log_name("SCarControl") 522 self.log_in_hCard() 523 dwControlCode = self.log_dwControlCode() 524 bSendLength = self.log_in2("bSendLength") 525 bSendBuffer = self.log_buffer("bSendBuffer", "in") 526 bRecvLength = self.log_out2("bRecvLength") 527 bRecvBuffer = self.log_buffer("bRecvBuffer", "out") 528 529 rv_text = self._log_rv() 530 531 # do not parse the received buffer in case of error 532 if rv_text != "SCARD_S_SUCCESS": 533 return 534 535 def hex2int(data, lengh): 536 return [int(x, 16) for x in data.split(" ")] 537 538 if dwControlCode == self.CM_IOCTL_GET_FEATURE_REQUEST: 539 print(" parsing CM_IOCTL_GET_FEATURE_REQUEST results:") 540 bRecvLength = int(bRecvLength, 16) 541 542 bRecvBuffer = hex2int(bRecvBuffer, bRecvLength) 543 544 # parse GET_FEATURE_REQUEST results 545 while bRecvBuffer: 546 tag = bRecvBuffer[0] 547 length = bRecvBuffer[1] 548 value = bRecvBuffer[2:2 + length] 549 value_int = value[3] + 256 * (value[2] + 256 550 * (value[1] + 256 * value[0])) 551 try: 552 self.ControlCodes[value_int] = self.features[tag] 553 self.__dict__[self.features[tag]] = value_int 554 except KeyError: 555 self.ControlCodes[value_int] = "UNKNOWN" 556 557 print(" Tag %s is 0x%X" % (self.ControlCodes[value_int], 558 value_int)) 559 560 bRecvBuffer = bRecvBuffer[2 + length:] 561 562 elif dwControlCode == self.FEATURE_GET_TLV_PROPERTIES: 563 print(" parsing FEATURE_GET_TLV_PROPERTIES results:") 564 bRecvLength = int(bRecvLength, 16) 565 566 bRecvBuffer = hex2int(bRecvBuffer, bRecvLength) 567 568 tlv_properties = { 569 1: "PCSCv2_PART10_PROPERTY_wLcdLayout", 570 2: "PCSCv2_PART10_PROPERTY_bEntryValidationCondition", 571 3: "PCSCv2_PART10_PROPERTY_bTimeOut2", 572 4: "PCSCv2_PART10_PROPERTY_wLcdMaxCharacters", 573 5: "PCSCv2_PART10_PROPERTY_wLcdMaxLines", 574 6: "PCSCv2_PART10_PROPERTY_bMinPINSize", 575 7: "PCSCv2_PART10_PROPERTY_bMaxPINSize", 576 8: "PCSCv2_PART10_PROPERTY_sFirmwareID", 577 9: "PCSCv2_PART10_PROPERTY_bPPDUSupport"} 578 579 # parse GET_TLV_PROPERTIES results 580 while bRecvBuffer: 581 tag = bRecvBuffer[0] 582 length = bRecvBuffer[1] 583 value = bRecvBuffer[2:2 + length] 584 585 try: 586 tag_text = tlv_properties[tag] 587 except KeyError: 588 tag_text = "UNKNOWN" 589 590 print(" Tag:", tag_text) 591 print(" Length: ", length) 592 print(" Value:", value) 593 594 bRecvBuffer = bRecvBuffer[2 + length:] 595 596 elif dwControlCode == self.FEATURE_IFD_PIN_PROPERTIES: 597 print(" parsing FEATURE_IFD_PIN_PROPERTIES results:") 598 bRecvBuffer = hex2int(bRecvBuffer, int(bRecvLength, 16)) 599 600 print(" wLcdLayout:", bRecvBuffer[0], bRecvBuffer[1]) 601 print(" bEntryValidationCondition:", bRecvBuffer[2]) 602 print(" bTimeOut2:", bRecvBuffer[3]) 603 604 elif dwControlCode == self.FEATURE_VERIFY_PIN_DIRECT: 605 print(" parsing FEATURE_VERIFY_PIN_DIRECT:") 606 bSendBuffer = hex2int(bSendBuffer, int(bSendLength, 16)) 607 608 print(" bTimerOut:", bSendBuffer[0]) 609 print(" bTimerOut2:", bSendBuffer[1]) 610 print(" bmFormatString:", bSendBuffer[2]) 611 print(" bmPINBlockString:", bSendBuffer[3]) 612 print(" bmPINLengthFormat:", bSendBuffer[4]) 613 print(" wPINMaxExtraDigit: 0x%02X%02X" % (bSendBuffer[6], 614 bSendBuffer[5])) 615 print(" Min:", bSendBuffer[6]) 616 print(" Max:", bSendBuffer[5]) 617 print(" bEntryValidationCondition:", bSendBuffer[7]) 618 print(" bNumberMessage:", bSendBuffer[8]) 619 print(" wLangId: 0x%02X%02X" % (bSendBuffer[10], 620 bSendBuffer[9])) 621 print(" bMsgIndex:", bSendBuffer[11]) 622 print(" bTeoPrologue:", bSendBuffer[12], bSendBuffer[13], \ 623 bSendBuffer[14]) 624 print(" ulDataLength:", bSendBuffer[15] + \ 625 bSendBuffer[16] * 256 + bSendBuffer[17] * 2 ** 16 + \ 626 bSendBuffer[18] * 2 ** 24) 627 print(" APDU:") 628 result = hexdump(bSendBuffer[19:]) 629 for line in result: 630 print(" ", line) 631 632 def _SCardGetAttrib(self): 633 """ SCardGetAttrib """ 634 self.log_name("SCardGetAttrib") 635 self.log_in_hCard() 636 self.log_in_attrid() 637 self.log_out2("bAttrLen") 638 self.log_buffer("bAttr", "out") 639 self._log_rv() 640 641 def _SCardSetAttrib(self): 642 """ SCardSetAttrib """ 643 self.log_name("SCardSetAttrib") 644 self.log_in_hCard() 645 self.log_in_attrid() 646 self.log_in2("bAttrLen") 647 self.log_buffer("bAttr", "in") 648 self._log_rv() 649 650 def _SCardStatus(self): 651 """ SCardStatus """ 652 self.log_name("SCardStatus") 653 self.log_in_hCard() 654 self.log_in2("pcchReaderLen") 655 self.log_in2("pcbAtrLen") 656 self.log_out2("cchReaderLen") 657 self.log_out2("mszReaderName") 658 self.log_out2("dwState") 659 self.log_out2("dwProtocol") 660 data = self.log_out2("bAtrLen") 661 if not data == "NULL": 662 self.log_out2("bAtr") 663 else: 664 self.log_out("bAtr") 665 self._log_rv() 666 667 def _SCardReconnect(self): 668 """ SCardReconnect """ 669 self.log_name("SCardReconnect") 670 self.log_in_hCard() 671 self.log_in_dwShareMode() 672 self.log_in_dwPreferredProtocols() 673 self.log_in2("dwInitialization") 674 self.log_out_dwActiveProtocol() 675 self._log_rv() 676 677 def _SCardDisconnect(self): 678 """" SCardDisconnect """ 679 self.log_name("SCardDisconnect") 680 self.log_in_hCard() 681 self.log_in_disposition() 682 self._log_rv() 683 684 def _SCardBeginTransaction(self): 685 """ SCardBeginTransaction """ 686 self.log_name("SCardBeginTransaction") 687 self.log_in_hCard() 688 self._log_rv() 689 690 def _SCardEndTransaction(self): 691 """ SCardEndTransaction """ 692 self.log_name("SCardEndTransaction") 693 self.log_in_hCard() 694 self.log_in_disposition() 695 self._log_rv() 696 697 def _SCardCancel(self): 698 """ SCardCancel """ 699 self.log_name("SCardCancel") 700 self.log_in_hCard() 701 self._log_rv() 702 703 def __init__(self, queue, stats, indent=0, color=True, diffable=False): 704 """ constructor """ 705 706 # communication queue 707 self.queue = queue 708 709 self.color = color 710 self.diffable = diffable 711 self.stats = stats 712 self.indent = " " * (indent * 4) 713 if display_thread: 714 self.indent += " (t%d) " % indent 715 716 self.features = {0x01: "FEATURE_VERIFY_PIN_START", 717 0x02: "FEATURE_VERIFY_PIN_FINISH", 718 0x03: "FEATURE_MODIFY_PIN_START", 719 0x04: "FEATURE_MODIFY_PIN_FINISH", 720 0x05: "FEATURE_GET_KEY_PRESSED", 721 0x06: "FEATURE_VERIFY_PIN_DIRECT", 722 0x07: "FEATURE_MODIFY_PIN_DIRECT", 723 0x08: "FEATURE_MCT_READER_DIRECT", 724 0x09: "FEATURE_MCT_UNIVERSAL", 725 0x0A: "FEATURE_IFD_PIN_PROPERTIES", 726 0x0B: "FEATURE_ABORT", 727 0x0C: "FEATURE_SET_SPE_MESSAGE", 728 0x0D: "FEATURE_VERIFY_PIN_DIRECT_APP_ID", 729 0x0E: "FEATURE_MODIFY_PIN_DIRECT_APP_ID", 730 0x0F: "FEATURE_WRITE_DISPLAY", 731 0x10: "FEATURE_GET_KEY", 732 0x11: "FEATURE_IFD_DISPLAY_PROPERTIES", 733 0x12: "FEATURE_GET_TLV_PROPERTIES", 734 0x13: "FEATURE_CCID_ESC_COMMAND"} 735 736 def SCARD_CTL_CODE(code): 737 return 0x42000000 + code 738 739 self.CM_IOCTL_GET_FEATURE_REQUEST = SCARD_CTL_CODE(3400) 740 self.ControlCodes = { 741 SCARD_CTL_CODE(1): "IOCTL_SMARTCARD_VENDOR_IFD_EXCHANGE", 742 SCARD_CTL_CODE(3400): "CM_IOCTL_GET_FEATURE_REQUEST" 743 } 744 # dwControlCode not yet known 745 for key in self.features.keys(): 746 self.__dict__[self.features[key]] = -1 747 748 def worker(self, *args): 749 line = self.queue.get() 750 while line != '': 751 # Enter function? 752 if line[0] != '>': 753 if line == 'EXIT': 754 return 755 else: 756 print("Garbage: ", line) 757 else: 758 try: 759 # dispatch 760 (direction, sec, usec, fct) = line.strip().split('|') 761 self.sec = int(sec) 762 self.usec = int(usec) 763 if fct == 'SCardEstablishContext': 764 self._SCardEstablishContext() 765 elif fct == 'SCardReleaseContext': 766 self._SCardReleaseContext() 767 elif fct == 'SCardIsValidContext': 768 self._SCardIsValidContext() 769 elif fct == 'SCardListReaderGroups': 770 self._SCardListReaderGroups() 771 elif fct == 'SCardFreeMemory': 772 self._SCardFreeMemory() 773 elif fct == 'SCardListReaders': 774 self._SCardListReaders() 775 elif fct == 'SCardGetStatusChange': 776 self._SCardGetStatusChange() 777 elif fct == 'SCardConnect': 778 self._SCardConnect() 779 elif fct == 'SCardTransmit': 780 self._SCardTransmit() 781 elif fct == 'SCardControl' or fct == 'SCardControl132': 782 self._SCardControl() 783 elif fct == 'SCardGetAttrib': 784 self._SCardGetAttrib() 785 elif fct == 'SCardSetAttrib': 786 self._SCardSetAttrib() 787 elif fct == 'SCardStatus': 788 self._SCardStatus() 789 elif fct == 'SCardReconnect': 790 self._SCardReconnect() 791 elif fct == 'SCardDisconnect': 792 self._SCardDisconnect() 793 elif fct == 'SCardBeginTransaction': 794 self._SCardBeginTransaction() 795 elif fct == 'SCardEndTransaction': 796 self._SCardEndTransaction() 797 elif fct == 'SCardCancel': 798 self._SCardCancel() 799 else: 800 print("Unknown function:", fct) 801 except SpyExit: 802 return 803 804 try: 805 record = self.stats[fct] 806 except KeyError: 807 record = self.stats[fct] = StatRecord(fct) 808 record.executions.append(self.execution_time) 809 810 line = self.queue.get() 811 812 813class PCSCdemultiplexer(object): 814 def __init__(self, logfile=None, color=True, diffable=False): 815 """ constructor """ 816 817 # use default fifo file? 818 if logfile is None: 819 logfile = os.path.expanduser('~/pcsc-spy') 820 821 # create the FIFO file 822 try: 823 os.mkfifo(logfile) 824 except OSError: 825 print("fifo %s already present. Reusing it." % logfile) 826 827 self.sec = self.usec = 0 828 829 self.fifo = logfile 830 self.filedesc2 = open(self.fifo, 'r') 831 832 self.queues = dict() 833 self.color = color 834 self.diffable = diffable 835 836 def __del__(self): 837 """ cleanup """ 838 from stat import S_ISFIFO 839 file_stat = os.stat(self.fifo) 840 841 # remove the log fifo only if it is a FIFO and not a log file 842 if S_ISFIFO(file_stat.st_mode): 843 os.unlink(self.fifo) 844 845 def loop(self): 846 """ loop reading logs """ 847 848 # for statistics 849 stats = dict() 850 851 threads = dict() 852 indent = 0 853 854 # dispatch 855 line = self.filedesc2.readline().strip() 856 857 (thread, tail) = line.split('@') 858 res = tail.strip().split('|') 859 # check the first line format 860 if res[0] != ">": 861 print("Wrong format!") 862 print("First line Should start with a '>' but got:") 863 print(tail) 864 return 865 (direction, sec, usec, fct) = res 866 867 start_time = int(sec) + int(usec) / 1000000. 868 869 lastest_result = "" 870 while line != '': 871 previous_thread = thread 872 (thread, tail) = line.split('@') 873 if "<" in tail: 874 lastest_result = tail 875 876 # in case the thread changes 877 if previous_thread != thread: 878 # schedule the other thread so it has time to empty its 879 # queue 880 time.sleep(.001) 881 882 try: 883 queue = self.queues[thread] 884 except KeyError: 885 queue = self.queues[thread] = Queue() 886 stats[thread] = dict() 887 # new worker 888 spy = PCSCspy(queue, stats[thread], indent=indent, 889 color=self.color, diffable=self.diffable) 890 threads[thread] = Thread(target=spy.worker) 891 threads[thread].start() 892 indent += 1 893 894 queue.put(tail) 895 896 line = self.filedesc2.readline().strip() 897 898 # tell the workers to exit 899 for thread in self.queues.keys(): 900 self.queues[thread].put('EXIT') 901 902 # wait for all the workers to finish 903 for thread in threads: 904 threads[thread].join() 905 906 (code, rv, sec, usec) = _parse_rv(lastest_result) 907 end_time = sec + usec / 1000000. 908 total_time = end_time - start_time 909 910 # compute some statistics 911 thread_n = 1 912 for thread in stats: 913 stat = stats[thread] 914 for fct in stat: 915 record = stat[fct] 916 record.occurences = len(record.executions) 917 record.total_time = sum(record.executions) 918 919 records = [stat[fct] for fct in stat] 920 921 # display statistics sorted by total_time 922 print() 923 print("Thread %d/%d" % (thread_n, len(stats))) 924 print("Results sorted by total execution time") 925 print("total time: %f sec" % total_time) 926 for record in sorted(records, key=attrgetter('total_time'), 927 reverse=True): 928 print("%f sec (%3d calls) %5.2f%% %s" % (record.total_time, 929 record.occurences, record.total_time / total_time * 100., 930 record.name)) 931 932 thread_n += 1 933 934 935def main(logfile=None, color=True, diffable=False): 936 """ main """ 937 spy = PCSCdemultiplexer(logfile, color, diffable) 938 spy.loop() 939 940 941def signal_handler(sig, frame): 942 print('Ctrl-C, exiting.') 943 os.kill(os.getpid(), signal.SIGQUIT) 944 945 946def print_usage(): 947 print("Usage: pcsc-spy [-n|--nocolor] [-d|--diffable] [-h|--help] [-v|--version] [-t|--thread]") 948 949if __name__ == "__main__": 950 import sys 951 import getopt 952 953 logfile = None 954 try: 955 opts, args = getopt.getopt(sys.argv[1:], "ndhvt", ["nocolor", 956 "diffable", "help", "version", "thread"]) 957 except getopt.GetoptError: 958 print_usage() 959 sys.exit(1) 960 961 color = True 962 diffable = False 963 display_thread = False 964 for o, a in opts: 965 if o == "-n" or o == "--nocolor": 966 color = False 967 if o == "-d" or o == "--diffable": 968 diffable = True 969 if o == "-h" or o == "--help": 970 print_usage() 971 sys.exit(1) 972 if o == "-v" or o == "--version": 973 print("pcsc-spy version 1.1") 974 print("Copyright (c) 2011-2021, Ludovic Rousseau <ludovic.rousseau@free.fr>") 975 print() 976 sys.exit(1) 977 if o == "-t" or o == "--thread": 978 display_thread = True 979 980 if len(args) > 0: 981 logfile = args[0] 982 983 signal.signal(signal.SIGINT, signal_handler) 984 main(logfile, color=color, diffable=diffable) 985