275 lines
9.1 KiB
Python
275 lines
9.1 KiB
Python
"""PCSCPart10: PC/SC Part 10 (pinpad)
|
|
|
|
__author__ = "Ludovic Rousseau"
|
|
|
|
Copyright 2009-2010 Ludovic Rosseau
|
|
Author: Ludovic Rousseau, mailto:ludovic.rousseau@free.fr
|
|
|
|
This file is part of pyscard.
|
|
|
|
pyscard is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
pyscard 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with pyscard; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
"""
|
|
|
|
from smartcard.scard import SCARD_CTL_CODE, SCARD_SHARE_DIRECT
|
|
|
|
# constants defined in PC/SC v2 Part 10
|
|
CM_IOCTL_GET_FEATURE_REQUEST = SCARD_CTL_CODE(3400)
|
|
|
|
FEATURE_VERIFY_PIN_START = 0x01
|
|
FEATURE_VERIFY_PIN_FINISH = 0x02
|
|
FEATURE_MODIFY_PIN_START = 0x03
|
|
FEATURE_MODIFY_PIN_FINISH = 0x04
|
|
FEATURE_GET_KEY_PRESSED = 0x05
|
|
FEATURE_VERIFY_PIN_DIRECT = 0x06
|
|
FEATURE_MODIFY_PIN_DIRECT = 0x07
|
|
FEATURE_MCT_READER_DIRECT = 0x08
|
|
FEATURE_MCT_UNIVERSAL = 0x09
|
|
FEATURE_IFD_PIN_PROPERTIES = 0x0A
|
|
FEATURE_ABORT = 0x0B
|
|
FEATURE_SET_SPE_MESSAGE = 0x0C
|
|
FEATURE_VERIFY_PIN_DIRECT_APP_ID = 0x0D
|
|
FEATURE_MODIFY_PIN_DIRECT_APP_ID = 0x0E
|
|
FEATURE_WRITE_DISPLAY = 0x0F
|
|
FEATURE_GET_KEY = 0x10
|
|
FEATURE_IFD_DISPLAY_PROPERTIES = 0x11
|
|
FEATURE_GET_TLV_PROPERTIES = 0x12
|
|
FEATURE_CCID_ESC_COMMAND = 0x13
|
|
|
|
Features = {
|
|
"FEATURE_VERIFY_PIN_START": FEATURE_VERIFY_PIN_START,
|
|
"FEATURE_VERIFY_PIN_FINISH": FEATURE_VERIFY_PIN_FINISH,
|
|
"FEATURE_MODIFY_PIN_START": FEATURE_MODIFY_PIN_START,
|
|
"FEATURE_MODIFY_PIN_FINISH": FEATURE_MODIFY_PIN_FINISH,
|
|
"FEATURE_GET_KEY_PRESSED": FEATURE_GET_KEY_PRESSED,
|
|
"FEATURE_VERIFY_PIN_DIRECT": FEATURE_VERIFY_PIN_DIRECT,
|
|
"FEATURE_MODIFY_PIN_DIRECT": FEATURE_MODIFY_PIN_DIRECT,
|
|
"FEATURE_MCT_READER_DIRECT": FEATURE_MCT_READER_DIRECT,
|
|
"FEATURE_MCT_UNIVERSAL": FEATURE_MCT_UNIVERSAL,
|
|
"FEATURE_IFD_PIN_PROPERTIES": FEATURE_IFD_PIN_PROPERTIES,
|
|
"FEATURE_ABORT": FEATURE_ABORT,
|
|
"FEATURE_SET_SPE_MESSAGE": FEATURE_SET_SPE_MESSAGE,
|
|
"FEATURE_VERIFY_PIN_DIRECT_APP_ID": FEATURE_VERIFY_PIN_DIRECT_APP_ID,
|
|
"FEATURE_MODIFY_PIN_DIRECT_APP_ID": FEATURE_MODIFY_PIN_DIRECT_APP_ID,
|
|
"FEATURE_WRITE_DISPLAY": FEATURE_WRITE_DISPLAY,
|
|
"FEATURE_GET_KEY": FEATURE_GET_KEY,
|
|
"FEATURE_IFD_DISPLAY_PROPERTIES": FEATURE_IFD_DISPLAY_PROPERTIES,
|
|
"FEATURE_GET_TLV_PROPERTIES": FEATURE_GET_TLV_PROPERTIES,
|
|
"FEATURE_CCID_ESC_COMMAND": FEATURE_CCID_ESC_COMMAND,
|
|
}
|
|
|
|
# properties returned by FEATURE_GET_TLV_PROPERTIES
|
|
PCSCv2_PART10_PROPERTY_wLcdLayout = 1
|
|
PCSCv2_PART10_PROPERTY_bEntryValidationCondition = 2
|
|
PCSCv2_PART10_PROPERTY_bTimeOut2 = 3
|
|
PCSCv2_PART10_PROPERTY_wLcdMaxCharacters = 4
|
|
PCSCv2_PART10_PROPERTY_wLcdMaxLines = 5
|
|
PCSCv2_PART10_PROPERTY_bMinPINSize = 6
|
|
PCSCv2_PART10_PROPERTY_bMaxPINSize = 7
|
|
PCSCv2_PART10_PROPERTY_sFirmwareID = 8
|
|
PCSCv2_PART10_PROPERTY_bPPDUSupport = 9
|
|
PCSCv2_PART10_PROPERTY_dwMaxAPDUDataSize = 10
|
|
PCSCv2_PART10_PROPERTY_wIdVendor = 11
|
|
PCSCv2_PART10_PROPERTY_wIdProduct = 12
|
|
|
|
Properties = {
|
|
"PCSCv2_PART10_PROPERTY_wLcdLayout": PCSCv2_PART10_PROPERTY_wLcdLayout,
|
|
"PCSCv2_PART10_PROPERTY_bEntryValidationCondition": PCSCv2_PART10_PROPERTY_bEntryValidationCondition, # pylint: disable=line-too-long
|
|
"PCSCv2_PART10_PROPERTY_bTimeOut2": PCSCv2_PART10_PROPERTY_bTimeOut2,
|
|
"PCSCv2_PART10_PROPERTY_wLcdMaxCharacters": PCSCv2_PART10_PROPERTY_wLcdMaxCharacters,
|
|
"PCSCv2_PART10_PROPERTY_wLcdMaxLines": PCSCv2_PART10_PROPERTY_wLcdMaxLines,
|
|
"PCSCv2_PART10_PROPERTY_bMinPINSize": PCSCv2_PART10_PROPERTY_bMinPINSize,
|
|
"PCSCv2_PART10_PROPERTY_bMaxPINSize": PCSCv2_PART10_PROPERTY_bMaxPINSize,
|
|
"PCSCv2_PART10_PROPERTY_sFirmwareID": PCSCv2_PART10_PROPERTY_sFirmwareID,
|
|
"PCSCv2_PART10_PROPERTY_bPPDUSupport": PCSCv2_PART10_PROPERTY_bPPDUSupport,
|
|
"PCSCv2_PART10_PROPERTY_dwMaxAPDUDataSize": PCSCv2_PART10_PROPERTY_dwMaxAPDUDataSize,
|
|
"PCSCv2_PART10_PROPERTY_wIdVendor": PCSCv2_PART10_PROPERTY_wIdVendor,
|
|
"PCSCv2_PART10_PROPERTY_wIdProduct": PCSCv2_PART10_PROPERTY_wIdProduct,
|
|
}
|
|
|
|
# we already have: Features['FEATURE_x'] = FEATURE_x
|
|
# we will now also have: Features[FEATURE_x] = 'FEATURE_x'
|
|
for k in list(Features.keys()):
|
|
Features[Features[k]] = k
|
|
|
|
for k in list(Properties.keys()):
|
|
Properties[Properties[k]] = k
|
|
|
|
|
|
def parseFeatureRequest(response):
|
|
"""Get the list of Part10 features supported by the reader.
|
|
|
|
@param response: result of L{CM_IOCTL_GET_FEATURE_REQUEST} command
|
|
|
|
@rtype: list
|
|
@return: a list of list C{[[tag1, value1], [tag2, value2]]}
|
|
"""
|
|
features = []
|
|
while len(response) > 0:
|
|
tag = response[0]
|
|
control = (
|
|
((((response[2] << 8) + response[3]) << 8) + response[4]) << 8
|
|
) + response[5]
|
|
try:
|
|
features.append([Features[tag], control])
|
|
except KeyError:
|
|
pass
|
|
del response[:6]
|
|
return features
|
|
|
|
|
|
def getFeatureRequest(cardConnection):
|
|
"""Get the list of Part10 features supported by the reader.
|
|
|
|
@param cardConnection: L{CardConnection} object
|
|
|
|
@rtype: list
|
|
@return: a list of list C{[[tag1, value1], [tag2, value2]]}
|
|
"""
|
|
response = cardConnection.control(CM_IOCTL_GET_FEATURE_REQUEST, [])
|
|
return parseFeatureRequest(response)
|
|
|
|
|
|
def hasFeature(featureList, feature):
|
|
"""return the controlCode for a feature or None
|
|
|
|
@param feature: feature to look for
|
|
@param featureList: feature list as returned by L{getFeatureRequest()}
|
|
|
|
@return: feature value or None
|
|
"""
|
|
for f in featureList:
|
|
if feature in (f[0], Features[f[0]]):
|
|
return f[1]
|
|
|
|
return None
|
|
|
|
|
|
def getPinProperties(cardConnection, featureList=None, controlCode=None):
|
|
"""return the C{PIN_PROPERTIES} structure
|
|
|
|
@param cardConnection: L{CardConnection} object
|
|
@param featureList: feature list as returned by L{getFeatureRequest()}
|
|
@param controlCode: control code for L{FEATURE_IFD_PIN_PROPERTIES}
|
|
|
|
@rtype: dict
|
|
@return: a dict"""
|
|
if controlCode is None:
|
|
if featureList is None:
|
|
featureList = getFeatureRequest(cardConnection)
|
|
controlCode = hasFeature(featureList, FEATURE_IFD_PIN_PROPERTIES)
|
|
|
|
if controlCode is None:
|
|
return {"raw": []}
|
|
|
|
response = cardConnection.control(controlCode, [])
|
|
d = {
|
|
"raw": response,
|
|
"LcdLayoutX": response[0],
|
|
"LcdLayoutY": response[1],
|
|
"EntryValidationCondition": response[2],
|
|
"TimeOut2": response[3],
|
|
}
|
|
|
|
return d
|
|
|
|
|
|
def getTlvProperties(cardConnection, featureList=None, controlCode=None):
|
|
"""return the C{GET_TLV_PROPERTIES} structure
|
|
|
|
@param cardConnection: L{CardConnection} object
|
|
@param featureList: feature list as returned by L{getFeatureRequest()}
|
|
@param controlCode: control code for L{FEATURE_GET_TLV_PROPERTIES}
|
|
|
|
@rtype: dict
|
|
@return: a dict"""
|
|
if controlCode is None:
|
|
if featureList is None:
|
|
featureList = getFeatureRequest(cardConnection)
|
|
controlCode = hasFeature(featureList, FEATURE_GET_TLV_PROPERTIES)
|
|
|
|
if controlCode is None:
|
|
return {"raw": []}
|
|
|
|
response = cardConnection.control(controlCode, [])
|
|
return parseTlvProperties(response)
|
|
|
|
|
|
def parseTlvProperties(response):
|
|
"""return the GET_TLV_PROPERTIES structure
|
|
|
|
@param response: result of L{FEATURE_GET_TLV_PROPERTIES}
|
|
|
|
@rtype: dict
|
|
@return: a dict"""
|
|
d = {
|
|
"raw": response,
|
|
}
|
|
|
|
# create a new list to consume it
|
|
tmp = list(response)
|
|
while tmp:
|
|
tag = tmp[0]
|
|
length = tmp[1]
|
|
data = tmp[2 : 2 + length]
|
|
|
|
if PCSCv2_PART10_PROPERTY_sFirmwareID == tag:
|
|
# convert to a string
|
|
data = "".join([chr(c) for c in data])
|
|
# we now suppose the value is an integer
|
|
elif 1 == length:
|
|
# byte
|
|
data = data[0]
|
|
elif 2 == length:
|
|
# 16 bits value
|
|
data = data[1] * 256 + data[0]
|
|
elif 4 == length:
|
|
# 32 bits value
|
|
data = ((data[3] * 256 + data[2]) * 256 + data[1]) * 256 + data[0]
|
|
|
|
# store the value in the dictionary
|
|
try:
|
|
d[Properties[tag]] = data
|
|
except KeyError:
|
|
d["UNKNOWN"] = data
|
|
|
|
del tmp[0 : 2 + length]
|
|
|
|
return d
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Small sample illustrating the use of PCSCPart10.
|
|
from smartcard.pcsc.PCSCReader import PCSCReader
|
|
|
|
cc = PCSCReader.readers()[0].createConnection()
|
|
cc.connect(mode=SCARD_SHARE_DIRECT)
|
|
|
|
# print(cc.control(CM_IOCTL_GET_FEATURE_REQUEST))
|
|
_features = getFeatureRequest(cc)
|
|
print(_features)
|
|
|
|
print(hasFeature(_features, FEATURE_VERIFY_PIN_START))
|
|
print(hasFeature(_features, FEATURE_VERIFY_PIN_DIRECT))
|
|
|
|
properties = getPinProperties(cc)
|
|
print("\nPinProperties:")
|
|
for k, v in list(properties.items()):
|
|
print(f" {k}: {v}")
|
|
|
|
print("\nTlvProperties:")
|
|
properties = getTlvProperties(cc)
|
|
for k, v in list(properties.items()):
|
|
print(f" {k}: {v}")
|