|
|
|
@ -35,10 +35,8 @@ import re
|
|
|
|
|
|
|
|
|
|
# smartcard python modules from pyscard |
|
|
|
|
from smartcard.CardType import AnyCardType |
|
|
|
|
from smartcard.CardType import ATRCardType |
|
|
|
|
from smartcard.CardRequest import CardRequest |
|
|
|
|
from smartcard.CardConnection import CardConnection |
|
|
|
|
from smartcard.CardConnectionObserver import ConsoleCardConnectionObserver |
|
|
|
|
from smartcard.ATR import ATR |
|
|
|
|
from smartcard.Exceptions import CardConnectionException |
|
|
|
|
from smartcard.util import toHexString |
|
|
|
@ -51,13 +49,13 @@ from card.utils import *
|
|
|
|
|
########################################################### |
|
|
|
|
|
|
|
|
|
class ISO7816(object): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
define attributes, methods and facilities for ISO-7816-4 standard smartcard |
|
|
|
|
|
|
|
|
|
use self.dbg = 1 or more to print live debugging information |
|
|
|
|
standard instructions codes available in "INS_dic" class dictionnary |
|
|
|
|
standard file tags available in "file_tags" class dictionnary |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
dbg = 0 |
|
|
|
|
|
|
|
|
@ -103,7 +101,7 @@ class ISO7816(object):
|
|
|
|
|
0xC0 : 'GET RESPONSE', |
|
|
|
|
0xC2 : 'ENVELOPE', |
|
|
|
|
0xC3 : 'ENVELOPE', |
|
|
|
|
0xCA : 'RETRIEVE DATA', |
|
|
|
|
0xCA : 'GET DATA', |
|
|
|
|
0xCB : 'RETRIEVE DATA', |
|
|
|
|
0xD2 : 'WRITE RECORD', |
|
|
|
|
0xD6 : 'UPDATE BINARY', |
|
|
|
@ -143,54 +141,46 @@ class ISO7816(object):
|
|
|
|
|
0xAB : 'Security Attribute expanded', |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
def __init__(self, atr=None, CLA=0x00): |
|
|
|
|
''' |
|
|
|
|
def __init__(self, CLA=0x00): |
|
|
|
|
""" |
|
|
|
|
connect smartcard and defines class CLA code for communication |
|
|
|
|
uses "pyscard" library services |
|
|
|
|
|
|
|
|
|
creates self.CLA attribute with CLA code |
|
|
|
|
and self.coms attribute with associated "apdu_stack" instance |
|
|
|
|
''' |
|
|
|
|
|
|
|
|
|
if (atr): |
|
|
|
|
cardtype = ATRCardType(atr) |
|
|
|
|
else: |
|
|
|
|
cardtype = AnyCardType() |
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
cardtype = AnyCardType() |
|
|
|
|
cardrequest = CardRequest(timeout=1, cardType=cardtype) |
|
|
|
|
self.cardservice = cardrequest.waitforcard() |
|
|
|
|
self.cardservice.connection.connect() |
|
|
|
|
self.reader = self.cardservice.connection.getReader() |
|
|
|
|
self.ATR = self.cardservice.connection.getATR() |
|
|
|
|
|
|
|
|
|
#observer = ConsoleCardConnectionObserver() |
|
|
|
|
#self.cardservice.connection.addObserver(observer) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.CLA = CLA |
|
|
|
|
self.coms = apdu_stack() |
|
|
|
|
|
|
|
|
|
def disconnect(self): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
disconnect smartcard: stops the session |
|
|
|
|
uses "pyscard" library service |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
self.cardservice.connection.disconnect() |
|
|
|
|
|
|
|
|
|
def define_class(self, CLA=0x00): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
define smartcard class attribute for APDU command |
|
|
|
|
override CLA value defined in class initialization |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
self.CLA = CLA |
|
|
|
|
|
|
|
|
|
def ATR_scan(self, smlist_file="/usr/share/pcsc/smartcard_list.txt"): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
print smartcard info retrieved from AnswerToReset |
|
|
|
|
thanks to pyscard routine |
|
|
|
|
|
|
|
|
|
if pcsc_scan is installed, |
|
|
|
|
use the signature file passed as argument for guessing the card |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
print('\nsmartcard reader: %s' % self.reader) |
|
|
|
|
if self.ATR != None: |
|
|
|
|
print('\nsmart card ATR is: %s' % toHexString(self.ATR)) |
|
|
|
@ -225,12 +215,12 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
def sw_status(sw1, sw2): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
sw_status(sw1=int, sw2=int) -> string |
|
|
|
|
|
|
|
|
|
SW status bytes interpretation as defined in ISO-7816 part 4 standard |
|
|
|
|
helps to speak and understand with the smartcard! |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
status = 'undefined status' |
|
|
|
|
if sw1 == 0x90 and sw2 == 0x00: status = 'normal processing: ' \ |
|
|
|
|
'command accepted: no further qualification' |
|
|
|
@ -326,7 +316,7 @@ class ISO7816(object):
|
|
|
|
|
return status |
|
|
|
|
|
|
|
|
|
def sr_apdu(self, apdu, force=False): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
sr_apdu(apdu=[0x.., 0x.., ...]) -> |
|
|
|
|
list [ string(apdu sent information), |
|
|
|
|
string(SW codes interpretation), |
|
|
|
@ -335,7 +325,7 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
generic function to send apdu, receive and interpret response |
|
|
|
|
force: force card reconnection if pyscard transmission fails |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if force: |
|
|
|
|
try: |
|
|
|
|
data, sw1, sw2 = self.cardservice.connection.transmit(apdu) |
|
|
|
@ -356,7 +346,7 @@ class ISO7816(object):
|
|
|
|
|
data ] |
|
|
|
|
|
|
|
|
|
def bf_cla(self, start=0, param=[0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
bf_cla( start=int(starting CLA), |
|
|
|
|
param=list(bytes for selecting file 0x3F, 0x00) ) -> |
|
|
|
|
list( CLA which could be supported ) |
|
|
|
@ -368,7 +358,7 @@ class ISO7816(object):
|
|
|
|
|
WARNING: |
|
|
|
|
can block the card definitively |
|
|
|
|
Do not do it with your own VISA / MASTERCARD |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
clist = [] |
|
|
|
|
for i in range(start, 256): |
|
|
|
|
ret = self.sr_apdu([i] + param) |
|
|
|
@ -379,7 +369,7 @@ class ISO7816(object):
|
|
|
|
|
return clist |
|
|
|
|
|
|
|
|
|
def bf_ins(self, start=0): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
bf_cla( start=int(starting INS) ) |
|
|
|
|
-> list( INS which could be supported ) |
|
|
|
|
|
|
|
|
@ -390,7 +380,7 @@ class ISO7816(object):
|
|
|
|
|
WARNING: |
|
|
|
|
can block the card definitively |
|
|
|
|
Do not do it with your own VISA / MASTERCARD |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
ilist = [] |
|
|
|
|
for i in range(start, 256): |
|
|
|
|
if self.dbg >= 3: |
|
|
|
@ -409,43 +399,43 @@ class ISO7816(object):
|
|
|
|
|
# ISO 7816 and described further in ETSI 101.221 |
|
|
|
|
### |
|
|
|
|
def READ_BINARY(self, P1=0x00, P2=0x00, Le=0x01): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to read the content of EF file with transparent structure |
|
|
|
|
Le: length of data bytes to be read |
|
|
|
|
|
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
READ_BINARY = [self.CLA, 0xB0, P1, P2, Le] |
|
|
|
|
return self.sr_apdu(READ_BINARY) |
|
|
|
|
|
|
|
|
|
def WRITE_BINARY(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to write the content of EF file with transparent structure |
|
|
|
|
|
|
|
|
|
Data: list of data bytes to be written |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
WRITE_BINARY = [self.CLA, 0xD0, P1, P2, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(WRITE_BINARY) |
|
|
|
|
|
|
|
|
|
def UPDATE_BINARY(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to update the content of EF file with transparent structure |
|
|
|
|
|
|
|
|
|
Data: list of data bytes to be written |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
UPDATE_BINARY = [self.CLA, 0xD6, P1, P2, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(UPDATE_BINARY) |
|
|
|
|
|
|
|
|
|
def ERASE_BINARY(self, P1=0x00, P2=0x00, Lc=None, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to erase the content of EF file with transparent structure |
|
|
|
|
|
|
|
|
|
Lc: 'None' or '0x02' |
|
|
|
|
Data: list of data bytes to be written |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if Lc is None: |
|
|
|
|
ERASE_BINARY = [self.CLA, 0x0E, P1, P2] |
|
|
|
|
else: |
|
|
|
@ -453,71 +443,71 @@ class ISO7816(object):
|
|
|
|
|
return self.sr_apdu(ERASE_BINARY) |
|
|
|
|
|
|
|
|
|
def READ_RECORD(self, P1=0x00, P2=0x00, Le=0x00): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to read the content of EF file with record structure |
|
|
|
|
|
|
|
|
|
P1: record number |
|
|
|
|
P2: reference control |
|
|
|
|
Le: length of data bytes to be read |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
READ_RECORD = [self.CLA, 0xB2, P1, P2, Le] |
|
|
|
|
return self.sr_apdu(READ_RECORD) |
|
|
|
|
|
|
|
|
|
def WRITE_RECORD(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to write the content of EF file with record structure |
|
|
|
|
|
|
|
|
|
P1: record number |
|
|
|
|
P2: reference control |
|
|
|
|
Data: list of data bytes to be written in the record |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
WRITE_RECORD = [self.CLA, 0xD2, P1, P2, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(WRITE_RECORD) |
|
|
|
|
|
|
|
|
|
def APPEND_RECORD(self, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to append a record on EF file with record structure |
|
|
|
|
|
|
|
|
|
P2: reference control |
|
|
|
|
Data: list of data bytes to be appended on the record |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APPEND_RECORD = [self.CLA, 0xE2, 0x00, P2, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(APPEND_RECORD) |
|
|
|
|
|
|
|
|
|
def UPDATE_RECORD(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to update the content of EF file with record structure |
|
|
|
|
|
|
|
|
|
P1: record number |
|
|
|
|
P2: reference control |
|
|
|
|
Data: list of data bytes to update the record |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APPEND_RECORD = [self.CLA, 0xDC, P1, P2, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(APPEND_RECORD) |
|
|
|
|
|
|
|
|
|
def GET_DATA(self, P1=0x00, P2=0x00, Le=0x01): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to retrieve data object |
|
|
|
|
|
|
|
|
|
P1 and P2: reference control for data object description |
|
|
|
|
Le: number of bytes expected in the response |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
GET_DATA = [self.CLA, 0xCA, P1, P2, Le] |
|
|
|
|
return self.sr_apdu(GET_DATA) |
|
|
|
|
|
|
|
|
|
def PUT_DATA(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to store data object |
|
|
|
|
|
|
|
|
|
P1 and P2: reference control for data object description |
|
|
|
|
Data: list of data bytes to put in the data object structure |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if len(Data) == 0: |
|
|
|
|
PUT_DATA = [self.CLA, 0xDA, P1, P2] |
|
|
|
|
elif 1 <= len(Data) <= 255: |
|
|
|
@ -529,26 +519,26 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
def SELECT_FILE(self, P1=0x00, P2=0x00, Data=[0x3F, 0x00], \ |
|
|
|
|
with_length=True): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to select file |
|
|
|
|
|
|
|
|
|
P1 and P2: selection control |
|
|
|
|
Data: list of bytes describing the file identifier or address |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if with_length: |
|
|
|
|
Data = [min(len(Data), 255)] + Data |
|
|
|
|
SELECT_FILE = [self.CLA, 0xA4, P1, P2] + Data |
|
|
|
|
return self.sr_apdu(SELECT_FILE) |
|
|
|
|
|
|
|
|
|
def VERIFY(self, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to verify user PIN, password or security codes |
|
|
|
|
|
|
|
|
|
P2: reference control |
|
|
|
|
Data: list of bytes to be verified by the card |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if len(Data) == 0: |
|
|
|
|
VERIFY = [self.CLA, 0x20, 0x00, P2] |
|
|
|
|
elif 1 <= len(Data) <= 255: |
|
|
|
@ -559,25 +549,25 @@ class ISO7816(object):
|
|
|
|
|
return self.sr_apdu(VERIFY) |
|
|
|
|
|
|
|
|
|
def INTERNAL_AUTHENTICATE(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to run internal authentication algorithm |
|
|
|
|
|
|
|
|
|
P1 and P2: reference control (algo, secret key selection...) |
|
|
|
|
Data: list of bytes containing the authentication challenge |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
INTERNAL_AUTHENTICATE = [self.CLA, 0x88, P1, P2, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(INTERNAL_AUTHENTICATE) |
|
|
|
|
|
|
|
|
|
def EXTERNAL_AUTHENTICATE(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to conditionally update the security status of the card |
|
|
|
|
after getting a challenge from it |
|
|
|
|
|
|
|
|
|
P1 and P2: reference control (algo, secret key selection...) |
|
|
|
|
Data: list of bytes containing the challenge response |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if len(Data) == 0: |
|
|
|
|
EXTERNAL_AUTHENTICATE = [self.CLA, 0x82, P1, P2] |
|
|
|
|
elif 1 <= len(Data) <= 255: |
|
|
|
@ -588,23 +578,23 @@ class ISO7816(object):
|
|
|
|
|
return self.sr_apdu(EXTERNAL_AUTHENTICATE) |
|
|
|
|
|
|
|
|
|
def GET_CHALLENGE(self): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to get a challenge for external entity authentication |
|
|
|
|
to the card |
|
|
|
|
|
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
GET_CHALLENGE = [self.CLA, 0x84, 0x00, 0x00] |
|
|
|
|
return self.sr_apdu(GET_CHALLENGE) |
|
|
|
|
|
|
|
|
|
def MANAGE_CHANNEL(self, P1=0x00, P2=0x00): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU to open and close supplementary logical channels |
|
|
|
|
|
|
|
|
|
P1=0x00 to open, 0x80 to close |
|
|
|
|
P2=0x00, 1, 2 or 3 to ask for logical channel number |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if (P1, P2) == (0x00, 0x00): |
|
|
|
|
MANAGE_CHANNEL = [self.CLA, 0x70, P1, P2, 0x01] |
|
|
|
|
else: |
|
|
|
@ -612,24 +602,24 @@ class ISO7816(object):
|
|
|
|
|
return self.sr_apdu(MANAGE_CHANNEL) |
|
|
|
|
|
|
|
|
|
def GET_RESPONSE(self, Le=0x01): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to retrieve data after selection |
|
|
|
|
or other kind of request that should get an extensive reply |
|
|
|
|
|
|
|
|
|
Le: expected length of data |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
GET_RESPONSE = [self.CLA, 0xC0, 0x00, 0x00, Le] |
|
|
|
|
return self.sr_apdu(GET_RESPONSE) |
|
|
|
|
|
|
|
|
|
def ENVELOPE(self, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to encapsulate data (APDU or other...) |
|
|
|
|
check ETSI TS 102.221 for some examples... |
|
|
|
|
|
|
|
|
|
Data: list of bytes |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if len(Data) == 0: |
|
|
|
|
ENVELOPE = [self.CLA, 0xC2, 0x00, 0x00] |
|
|
|
|
elif 1 <= len(Data) <= 255: |
|
|
|
@ -637,7 +627,7 @@ class ISO7816(object):
|
|
|
|
|
return self.sr_apdu(ENVELOPE) |
|
|
|
|
|
|
|
|
|
def SEARCH_RECORD(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to seach pattern in the current EF file |
|
|
|
|
with record structure |
|
|
|
|
|
|
|
|
@ -645,36 +635,36 @@ class ISO7816(object):
|
|
|
|
|
P2: type of search |
|
|
|
|
Data: list of bytes describing a pattern to search for |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
SEARCH_RECORD = [self.CLA, 0xA2, P1, P2, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(SEARCH_RECORD) |
|
|
|
|
|
|
|
|
|
def DISABLE_CHV(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to disable CHV verification (such as PIN or password...) |
|
|
|
|
|
|
|
|
|
P1: let to 0x00... or read ISO and ETSI specifications |
|
|
|
|
P2: type of CHV to disable |
|
|
|
|
Data: list of bytes for CHV value |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
DISABLE_CHV = [self.CLA, 0x26, P1, P2, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(DISABLE_CHV) |
|
|
|
|
|
|
|
|
|
def ENABLE_CHV(self, P1=0x00, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to enable CHV verification (such as PIN or password...) |
|
|
|
|
|
|
|
|
|
P1: let to 0x00... or read ISO and ETSI specifications |
|
|
|
|
P2: type of CHV to enable |
|
|
|
|
Data: list of bytes for CHV value |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
ENABLE_CHV = [self.CLA, 0x28, P1, P2, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(ENABLE_CHV) |
|
|
|
|
|
|
|
|
|
def UNBLOCK_CHV(self, P2=0x00, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
APDU command to unblock CHV code (e.g. with PUK for deblocking PIN) |
|
|
|
|
|
|
|
|
|
P2: type of CHV to unblock |
|
|
|
@ -682,25 +672,45 @@ class ISO7816(object):
|
|
|
|
|
Data: if Lc=0x10, UNBLOCK_CHV (PUK) value and new CHV (PIN) values, |
|
|
|
|
each are 8 digits |
|
|
|
|
call sr_apdu method |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if len(Data) != 16: |
|
|
|
|
UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2] |
|
|
|
|
else: |
|
|
|
|
UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2, 0x10] + Data |
|
|
|
|
return self.sr_apdu(UNBLOCK_CHV) |
|
|
|
|
|
|
|
|
|
def FETCH(self, Le=0x01): |
|
|
|
|
""" |
|
|
|
|
APDU command to receive an ICC proactive command |
|
|
|
|
that will need to be responded by a TERMINAL RESPONSE |
|
|
|
|
|
|
|
|
|
Le: expected length of data |
|
|
|
|
call sr_apdu method |
|
|
|
|
""" |
|
|
|
|
FETCH = [self.CLA, 0x12, 0x00, 0x00, Le] |
|
|
|
|
return self.sr_apdu(FETCH) |
|
|
|
|
|
|
|
|
|
def TERMINAL_RESPONSE(self, Data=[]): |
|
|
|
|
""" |
|
|
|
|
APDU command to provide a response to an ICC proactive command |
|
|
|
|
|
|
|
|
|
Data: list of bytes for the response to be provided to the ICC |
|
|
|
|
""" |
|
|
|
|
TERMINAL_RESP = [self.CLA, 0x14, 0x00, 0x00, len(Data)] + Data |
|
|
|
|
return self.sr_apdu(TERMINAL_RESP) |
|
|
|
|
|
|
|
|
|
########################## |
|
|
|
|
# evolved "macro" method for ISO7816 card |
|
|
|
|
# need the "coms" attribute being an apdu_stack() |
|
|
|
|
########################## |
|
|
|
|
def parse_file(self, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
parse_file(self, Data) -> Dict() |
|
|
|
|
|
|
|
|
|
parses a list of bytes returned when selecting a file |
|
|
|
|
interprets the content of some informative bytes |
|
|
|
|
for file structure and parsing method... |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
ber = BERTLV_parser( Data ) |
|
|
|
|
if self.dbg >= 3: |
|
|
|
|
log(3, 'BER structure:\n%s' % ber) |
|
|
|
@ -744,13 +754,13 @@ class ISO7816(object):
|
|
|
|
|
return fil |
|
|
|
|
|
|
|
|
|
def parse_FCP(self, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
parse_FCP(Data) -> Dict() |
|
|
|
|
|
|
|
|
|
parses a list of bytes returned when selecting a file |
|
|
|
|
interprets the content of some informative bytes |
|
|
|
|
for file structure and parsing method... |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
fil = {} |
|
|
|
|
# loop on the Data bytes to parse TLV'style attributes |
|
|
|
|
toProcess = Data |
|
|
|
@ -817,11 +827,11 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
def parse_life_cycle(Data, fil): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
parses a list of bytes provided in Data |
|
|
|
|
interprets the content as the life cycle |
|
|
|
|
and enriches the file dictionnary passed as argument |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if Data[0] == 1: fil['Life Cycle Status'] = 'creation state' |
|
|
|
|
elif Data[0] == 3: fil['Life Cycle Status'] = 'initialization state' |
|
|
|
|
elif Data[0] in (5, 7): fil['Life Cycle Status'] = 'operational state' \ |
|
|
|
@ -836,11 +846,11 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
def parse_file_descriptor(Data, fil): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
parses a list of bytes provided in Data |
|
|
|
|
interprets the content as the file descriptor |
|
|
|
|
and enriches the file dictionnary passed as argument |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# parse the File Descriptor Byte |
|
|
|
|
fd = Data[0] |
|
|
|
|
fd_type = (fd >> 3) & 0b00111 |
|
|
|
@ -881,11 +891,11 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
def parse_proprietary(Data, fil): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
parses a list of bytes provided in Data |
|
|
|
|
interprets the content as the proprietary parameters |
|
|
|
|
and enriches the file dictionnary passed as argument |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
propr_tags = { |
|
|
|
|
0x80:"UICC characteristics", |
|
|
|
|
0x81:"Application power consumption", |
|
|
|
@ -904,13 +914,12 @@ class ISO7816(object):
|
|
|
|
|
Data = Data[L+2:] |
|
|
|
|
return fil |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_compact_security_attribute(self, Data, fil): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
parses a list of bytes provided in Data |
|
|
|
|
interprets the content as the compact form for security parameters |
|
|
|
|
and enriches the file dictionnary passed as argument |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# See ISO-IEC 7816-4 section 5.4.3, with compact and expanded format |
|
|
|
|
AM = Data[0] |
|
|
|
|
SC = Data[1:] |
|
|
|
@ -997,10 +1006,10 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
def parse_expanded_security_attribute(Data, fil): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
check references to EF_ARR file containing access conditions |
|
|
|
|
see ISO 7816-4 |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# self.ARR = {ARR_id:[ARR_content],...} |
|
|
|
|
return fil |
|
|
|
|
file_length = len(Data) |
|
|
|
@ -1023,23 +1032,23 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
def parse_security_attribute(Data, fil): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
TODO: to implement... |
|
|
|
|
|
|
|
|
|
need to work further on how to do it (with ref to EF_ARR) |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# See ISO-IEC 7816-4 section 5.4.3, with compact and expanded format |
|
|
|
|
# not implemented yet (looks like useless for (U)SIM card ?) |
|
|
|
|
return fil |
|
|
|
|
|
|
|
|
|
def parse_FCI(self, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
parse_FCI(Data) -> Dict() |
|
|
|
|
|
|
|
|
|
parses a list of bytes returned when selecting a file |
|
|
|
|
interprets the content of some informative bytes |
|
|
|
|
for file structure and parsing method... |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
fil = {} |
|
|
|
|
# loop on the Data bytes to parse TLV'style attributes |
|
|
|
|
toProcess = Data |
|
|
|
@ -1112,13 +1121,13 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_EF(self, fil): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
interprets the content of file parameters (Structure, Size, Length...) |
|
|
|
|
and enriches the file dictionnary passed as argument |
|
|
|
|
with "Data" key and corresponding |
|
|
|
|
- list of bytes for EF transparent |
|
|
|
|
- list of list of bytes for cyclic or linear EF |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# read EF transparent data |
|
|
|
|
if fil['Structure'] == 'transparent': |
|
|
|
|
self.coms.push( self.READ_BINARY(Le=fil['Size']) ) |
|
|
|
@ -1133,7 +1142,7 @@ class ISO7816(object):
|
|
|
|
|
fil['Data'] = [] |
|
|
|
|
# for record data: need to check the number of recordings |
|
|
|
|
# stored in the file, and iterate for each |
|
|
|
|
for i in range( (fil['Size'] / fil['Record Length']) ): |
|
|
|
|
for i in range( (fil['Size'] // fil['Record Length']) ): |
|
|
|
|
self.coms.push( self.READ_RECORD(P1=i+1, P2=0x04, \ |
|
|
|
|
Le=fil['Record Length']) ) |
|
|
|
|
if self.coms()[2] != (0x90, 0x00): |
|
|
|
@ -1154,7 +1163,7 @@ class ISO7816(object):
|
|
|
|
|
return fil |
|
|
|
|
|
|
|
|
|
def select(self, addr=[0x3F, 0x00], type="fid", with_length=True): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
self.select(addr=[0x.., 0x..], type="fid", with_length=True) |
|
|
|
|
-> dict() on success, None on error |
|
|
|
|
|
|
|
|
@ -1181,7 +1190,7 @@ class ISO7816(object):
|
|
|
|
|
in the SELECT_FILE APDU |
|
|
|
|
|
|
|
|
|
APDUs exchanged available thanks to the attribute `self`.coms |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# get the UICC trigger |
|
|
|
|
is_UICC = isinstance(self, UICC) |
|
|
|
|
|
|
|
|
@ -1228,7 +1237,7 @@ class ISO7816(object):
|
|
|
|
|
# take the parse_file() method from the instance: |
|
|
|
|
# ISO7816, UICC (for USIM) or SIM |
|
|
|
|
file = self.parse_file(data) |
|
|
|
|
if file['Type'][0:2] == 'EF': |
|
|
|
|
if 'Type' in file.keys() and file['Type'][0:2] == 'EF': |
|
|
|
|
file = self.read_EF(file) |
|
|
|
|
|
|
|
|
|
# finally returns the whole file dictionnary, |
|
|
|
@ -1240,14 +1249,14 @@ class ISO7816(object):
|
|
|
|
|
############### |
|
|
|
|
|
|
|
|
|
def go_to_path(self, path=[], under_AID=None): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
self.go_to_path(path=[0x.., 0x.., 0x.., 0x.., ..], under_AID=None) |
|
|
|
|
-> void |
|
|
|
|
|
|
|
|
|
selects all DF addresses successively from the path given |
|
|
|
|
uses the .select() method with "fid" as selection type |
|
|
|
|
works with AID number too |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# check path length |
|
|
|
|
if len(path) % 2: |
|
|
|
|
log(1, '(go_to_path) path length not correct: %s' % path) |
|
|
|
@ -1273,13 +1282,13 @@ class ISO7816(object):
|
|
|
|
|
# this helps to build the blacklist: |
|
|
|
|
|
|
|
|
|
def make_blacklist(self, DF_path=[], under_AID=None): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
self.make_blacklist(DF_path=[0x.., 0x.., 0x.., 0x..], under_AID=None) |
|
|
|
|
-> list( DFs ) |
|
|
|
|
|
|
|
|
|
check dictionnaries describing MF or AID directory structure |
|
|
|
|
and return DF not to select when scanning for file ID under a DF |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# IC card Master File, never reselect it... |
|
|
|
|
MF = [0x3F, 0x00] |
|
|
|
|
# you should also avoid to reselect it |
|
|
|
@ -1326,7 +1335,7 @@ class ISO7816(object):
|
|
|
|
|
|
|
|
|
|
def scan_DF(self, dir_path=[], under_AID=None, \ |
|
|
|
|
hi_addr=(0, 0xff), lo_addr=(0, 0xff)): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
self.scan_DF(dir_path=[0x.., 0x.., 0x.., 0x..], under_AID=None) |
|
|
|
|
-> list(filesystem), list(child_DF) |
|
|
|
|
|
|
|
|
@ -1335,7 +1344,7 @@ class ISO7816(object):
|
|
|
|
|
lo_addr: 8 LSB of the file address to brute force |
|
|
|
|
avoid selecting blacklisted files (MF, parent_DF, brother_DF, current_DF) |
|
|
|
|
return list of all found files (EF, DF) and list of child DF |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# build blacklist of addresses from the current directory structure |
|
|
|
|
# and selected path, in order to select only child file ID: |
|
|
|
|
BL = self.make_blacklist(dir_path, under_AID) |
|
|
|
@ -1387,7 +1396,7 @@ class ISO7816(object):
|
|
|
|
|
return FS, child_DF |
|
|
|
|
|
|
|
|
|
def explore_DF(self, DF_path=[], under_AID=None, recursive=True): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
self.explore_DF(dir_path=[0x.., 0x.., 0x.., 0x..], under_AID=None, \ |
|
|
|
|
recursive=True) |
|
|
|
|
-> None |
|
|
|
@ -1397,7 +1406,7 @@ class ISO7816(object):
|
|
|
|
|
a certain level) |
|
|
|
|
fill in self.FS dictionnary with found DF and files |
|
|
|
|
and self._MF_struct or self._AID`num`_struct with directory structure |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# init by scanning the given DF_path (MF or AID) |
|
|
|
|
FS, child_DF = self.scan_DF(DF_path, under_AID) |
|
|
|
|
# then init or extend self._MF_struct or |
|
|
|
@ -1439,19 +1448,22 @@ class ISO7816(object):
|
|
|
|
|
############################################## |
|
|
|
|
|
|
|
|
|
class UICC(ISO7816): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
define attributes, methods and facilities for ETSI UICC card |
|
|
|
|
check UICC specifications mainly in ETSI TS 102.221 |
|
|
|
|
|
|
|
|
|
inherits (eventually overrides) methods and objects from ISO7816 class |
|
|
|
|
use self.dbg = 1 or more to print live debugging information |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
AID_RID = { |
|
|
|
|
(0xA0, 0x00, 0x00, 0x00, 0x09): 'ETSI', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x00, 0x87): '3GPP', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x03, 0x43): '3GPP2', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x06, 0x45): 'OneM2M', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x04, 0x12): 'OMA', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x04, 0x24): 'WiMAX', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x00, 0x03): 'GlobalPlatform', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x01, 0x51): 'GlobalPlatform' |
|
|
|
|
} |
|
|
|
|
AID_ETSI_app_code = { |
|
|
|
|
(0x00, 0x00): 'Reserved', |
|
|
|
@ -1469,16 +1481,32 @@ class UICC(ISO7816):
|
|
|
|
|
(0x10, 0x04): 'ISIM', |
|
|
|
|
(0x10, 0x05): 'USIM API for JavaCard', |
|
|
|
|
(0x10, 0x06): 'ISIM API for JavaCard', |
|
|
|
|
(0x10, 0x05): 'Contact Manager API for JavaCard', |
|
|
|
|
(0x10, 0x07): 'Contact Manager API for JavaCard', |
|
|
|
|
(0x10, 0x08): '3GPP USIM-INI', |
|
|
|
|
(0x10, 0x09): '3GPP USIM-RN', |
|
|
|
|
(0x10, 0x0A): '3GPP HPSIM', |
|
|
|
|
} |
|
|
|
|
# TODO: check USIM specific AID as defined in TS 31.130, annex C |
|
|
|
|
AID_3GPP2_app_code = { |
|
|
|
|
(0x10, 0x02): 'CSIM', |
|
|
|
|
} |
|
|
|
|
AID_OneM2M_app_code = { |
|
|
|
|
(0x10, 0x01): 'oneM2M UICC', |
|
|
|
|
(0x10, 0x02): 'oneM2M 1M2MSM', |
|
|
|
|
} |
|
|
|
|
AID_country_code = { |
|
|
|
|
(0xFF, 0x33): 'France', |
|
|
|
|
(0xFF, 0x44): 'United Kingdom', |
|
|
|
|
(0xFF, 0x49): 'Germany', |
|
|
|
|
} |
|
|
|
|
AID_GP_code = { |
|
|
|
|
(0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00): 'GlobalPlatform card manager (before v211)', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00): 'GlobalPlatform card manager (before v211)', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x01, 0x51, 0x00, 0x00): 'GlobalPlatform card manager (v211 and after)', |
|
|
|
|
(0xA0, 0x00, 0x00, 0x00, 0x18, 0x43, 0x4D, 0x00): 'GlobalPlatform card manager (GemXpresso Pro)' |
|
|
|
|
} |
|
|
|
|
# TODO: check UICC access control AID as defined in the Android API |
|
|
|
|
#https://source.android.com/devices/tech/config/uicc |
|
|
|
|
|
|
|
|
|
pin_status = { |
|
|
|
|
0x01 : "PIN Appl 1", |
|
|
|
@ -1535,19 +1563,20 @@ class UICC(ISO7816):
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
def __init__(self): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
initializes like an ISO7816-4 card with CLA=0x00 |
|
|
|
|
initialized on the MF |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
ISO7816.__init__(self, CLA=0x00) |
|
|
|
|
self.AID = [] |
|
|
|
|
self.AID_GP = {} |
|
|
|
|
|
|
|
|
|
if self.dbg >= 2: |
|
|
|
|
log(3, '(UICC.__init__) type definition: %s' % type(self)) |
|
|
|
|
log(3, '(UICC.__init__) CLA definition: %s' % hex(self.CLA)) |
|
|
|
|
|
|
|
|
|
def parse_file(self, Data=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file) |
|
|
|
|
mainly based on the ISO7816 parsing style |
|
|
|
|
|
|
|
|
@ -1555,7 +1584,7 @@ class UICC(ISO7816):
|
|
|
|
|
interprets the content of some informative bytes for right accesses, |
|
|
|
|
type / format of file... see TS 102.221 |
|
|
|
|
works over the UICC file structure (quite different from e.g. SIM card) |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
# First ISO7816 parsing |
|
|
|
|
fil = ISO7816.parse_file(self, Data) |
|
|
|
|
|
|
|
|
@ -1574,11 +1603,11 @@ class UICC(ISO7816):
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
def parse_pin_status(Data, fil): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
parses a list of bytes provided in Data |
|
|
|
|
interprets the content as the UICC pin status |
|
|
|
|
and enriches the file dictionnary passed as argument |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
PS_DO = Data[2:2+Data[1]] |
|
|
|
|
Data = Data[2+len(PS_DO):] |
|
|
|
|
PIN_status = '' |
|
|
|
@ -1620,13 +1649,12 @@ class UICC(ISO7816):
|
|
|
|
|
return fil |
|
|
|
|
|
|
|
|
|
def get_AID(self): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
checks EF_DIR at the MF level, |
|
|
|
|
and available AID (Application ID) referenced |
|
|
|
|
|
|
|
|
|
puts it into self.AID |
|
|
|
|
interprets and print the content of the self.AID list |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
#go back to MF and select EF_DIR |
|
|
|
|
#self.select(addr=[]) |
|
|
|
|
|
|
|
|
@ -1649,9 +1677,9 @@ class UICC(ISO7816):
|
|
|
|
|
|
|
|
|
|
@staticmethod |
|
|
|
|
def interpret_AID(aid=[]): |
|
|
|
|
''' |
|
|
|
|
interprets and prints the aid provided |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
returns a string with the interpretation of the AID provided |
|
|
|
|
""" |
|
|
|
|
if len(aid) < 11: |
|
|
|
|
return |
|
|
|
|
# check AID format |
|
|
|
@ -1670,6 +1698,9 @@ class UICC(ISO7816):
|
|
|
|
|
if aid_rid == (0xA0, 0x00, 0x00, 0x03, 0x43) \ |
|
|
|
|
and aid_app in UICC.AID_3GPP2_app_code.keys(): |
|
|
|
|
aid_app = UICC.AID_3GPP2_app_code[aid_app] |
|
|
|
|
if aid_rid == (0xA0, 0x00, 0x00, 0x06, 0x45) \ |
|
|
|
|
and aid_app in UICC.AID_OneM2M_app_code.keys(): |
|
|
|
|
aid_app = UICC.AID_OneM2M_app_code[aid_app] |
|
|
|
|
# get AID responsible SDO and country |
|
|
|
|
if aid_rid in UICC.AID_RID.keys(): |
|
|
|
|
aid_rid = UICC.AID_RID[aid_rid] |
|
|
|
@ -1679,11 +1710,25 @@ class UICC(ISO7816):
|
|
|
|
|
return('%s || %s || %s || %s || %s' \ |
|
|
|
|
% (aid_rid, aid_app, aid_country, aid_provider, tuple(aid[11:]))) |
|
|
|
|
|
|
|
|
|
def get_AID_GP(self): |
|
|
|
|
""" |
|
|
|
|
tries to select all AID addresses from AID_GP_app_code at the MF level |
|
|
|
|
|
|
|
|
|
puts those to which there is a positive SW response into self.AID_GP |
|
|
|
|
""" |
|
|
|
|
for aid in self.AID_GP_code.keys(): |
|
|
|
|
aid = list(aid) |
|
|
|
|
self.select_by_name(aid) |
|
|
|
|
if self.coms()[2] == (0x90, 0x00): |
|
|
|
|
# positive response, where we could read the data returned by |
|
|
|
|
# the application |
|
|
|
|
self.AID_GP[tuple(aid)] = BERTLV_extract(self.coms()[3]) |
|
|
|
|
|
|
|
|
|
def get_ICCID(self): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
check EF_ICCID at the MF level, |
|
|
|
|
and returnq the ASCII value of the ICCID |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
#go back to MF and select EF_ICCID |
|
|
|
|
#self.select(addr=[]) |
|
|
|
|
|
|
|
|
@ -1696,15 +1741,15 @@ class UICC(ISO7816):
|
|
|
|
|
return decode_BCD( EF_ICCID['Data'] ) |
|
|
|
|
|
|
|
|
|
def select_by_name(self, name=[]): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
AID selection by name: should be AID bytes |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
return self.select(name, 'aid') |
|
|
|
|
|
|
|
|
|
def select_by_aid(self, aid_num=1): |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
AID selection by index |
|
|
|
|
''' |
|
|
|
|
""" |
|
|
|
|
if hasattr(self, 'AID') and aid_num <= len(self.AID)+1: |
|
|
|
|
return self.select(self.AID[aid_num-1], 'aid') |
|
|
|
|
|
|
|
|
|