diff --git a/card/ICC.py b/card/ICC.py index f55bfcc..9a0d7ec 100644 --- a/card/ICC.py +++ b/card/ICC.py @@ -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') diff --git a/card/SIM.py b/card/SIM.py index 4065167..2e6c9c7 100644 --- a/card/SIM.py +++ b/card/SIM.py @@ -90,21 +90,22 @@ SIM_service_table = { 56 : "Service Provider Display Information", } + class SIM(ISO7816): - ''' + """ define attributes, methods and facilities for ETSI / 3GPP SIM card check SIM specifications in ETSI TS 102.221 and 3GPP TS 51.011 inherit methods and objects from ISO7816 class use self.dbg = 1 or more to print live debugging information - ''' + """ - def __init__(self, atr = None): - ''' + def __init__(self): + """ initialize like an ISO7816-4 card with CLA=0xA0 can also be used for USIM working in SIM mode, - ''' - ISO7816.__init__(self, atr, CLA=0xA0) + """ + ISO7816.__init__(self, CLA=0xA0) if self.dbg >= 2: log(3, '(SIM.__init__) type definition: %s' % type(self)) @@ -112,13 +113,13 @@ class SIM(ISO7816): @staticmethod def sw_status(sw1, sw2): - ''' + """ sw_status(sw1=int, sw2=int) -> string extends SW status bytes interpretation from ISO7816 with ETSI / 3GPP SW codes helps to speak with the smartcard! - ''' + """ status = ISO7816.sw_status(sw1, sw2) if sw1 == 0x91: status = 'normal processing, with extra info ' \ 'containing a command for the terminal: length of the ' \ @@ -158,10 +159,10 @@ class SIM(ISO7816): return status def verify_pin(self, pin='', pin_type=1): - ''' + """ verify CHV1 (PIN code) or CHV2 with VERIFY APDU command call ISO7816 VERIFY method - ''' + """ if pin_type in [1, 2] and type(pin) is str and \ len(pin) == 4 and 0 <= int(pin) < 10000: PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF] @@ -171,12 +172,12 @@ class SIM(ISO7816): log(2, '(verify_pin) bad input parameters') def disable_pin(self, pin='', pin_type=1): - ''' + """ disable CHV1 (PIN code) or CHV2 with DISABLE_CHV APDU command TIP: do it as soon as you can when you are working with a SIM / USIM card for which you know the PIN! call ISO7816 DISABLE method - ''' + """ if pin_type in [1, 2] and type(pin) is str and \ len(pin) == 4 and 0 <= int(pin) < 10000: PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF] @@ -186,10 +187,10 @@ class SIM(ISO7816): log(2, '(disable_pin) bad input parameters') def enable_pin(self, pin='', pin_type=1): - ''' + """ enable CHV1 (PIN code) or CHV2 with ENABLE_CHV APDU command call ISO7816 ENABLE method - ''' + """ if pin_type in [1, 2] and type(pin) is str and \ len(pin) == 4 and 0 <= int(pin) < 10000: PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF] @@ -199,7 +200,7 @@ class SIM(ISO7816): log(2, '(enable_pin) bad input parameters') def unblock_pin(self, pin_type=1, unblock_pin=''): - ''' + """ WARNING: not correctly implemented!!! and PUK are in general 8 nums... TODO: make it correctly! @@ -208,7 +209,7 @@ class SIM(ISO7816): unblock CHV1 (PIN code) or CHV2 with UNBLOCK_CHV APDU command and set 0000 value for new PIN call ISO7816 UNBLOCK_CHV method - ''' + """ log(1, '(unblock_pin) not implemented: aborting') return #if pin_type == 1: @@ -225,14 +226,14 @@ class SIM(ISO7816): #return self.UNBLOCK_CHV(P2=pin_type) def parse_file(self, Data=[]): - ''' + """ parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file) parses a list of bytes returned when selecting a file interprets the content of some informative bytes for right accesses, type / format of file... see TS 51.011 works over the SIM file structure - ''' + """ fil = {} fil['Size'] = Data[2]*0x100 + Data[3] fil['File Identifier'] = Data[4:6] @@ -243,16 +244,16 @@ class SIM(ISO7816): fil['EF_num'] = Data[15] fil['codes_num'] = Data[16] fil['CHV1'] = ('not initialized','initialized')\ - [(Data[18] & 0x80) / 0x80]\ + [Data[18] >> 7]\ + ': %d attempts remain' % (Data[18] & 0x0F) fil['unblock_CHV1'] = ('not initialized','initialized')\ - [(Data[19] & 0x80) / 0x80]\ + [Data[19] >> 7]\ + ': %d attempts remain' % (Data[19] & 0x0F) fil['CHV2'] = ('not initialized','initialized')\ - [(Data[20] & 0x80) / 0x80]\ + [Data[20] >> 7]\ + ': %d attempts remain' % (Data[20] & 0x0F) fil['unblock_CHV2'] = ('not initialized','initialized')\ - [(Data[21] & 0x80) / 0x80]\ + [Data[21] >> 7]\ + ': %d attempts remain' % (Data[21] & 0x0F) if len(Data) > 23: fil['Adm'] = Data[23:] @@ -279,7 +280,7 @@ class SIM(ISO7816): return fil def run_gsm_alg(self, RAND=16*[0x00]): - ''' + """ self.run_gsm_alg( RAND ) -> ( SRES, Kc ) RAND : list of bytes, length 16 SRES : list of bytes, length 4 @@ -289,7 +290,7 @@ class SIM(ISO7816): accepts any kind of RAND (old GSM fashion) feed with RAND 16 bytes value returns a list with SRES and Kc, or None on error - ''' + """ if len(RAND) != 16: if self.dbg: log(1, '(run_gsm_alg) bad RAND value: aborting') @@ -316,12 +317,12 @@ class SIM(ISO7816): return [ SRES, Kc ] def get_imsi(self): - ''' + """ self.get_imsi() -> string(IMSI) reads IMSI value at address [0x6F, 0x07] returns IMSI string on success or None on error - ''' + """ # select DF_GSM for SIM card self.select([0x7F, 0x20]) if self.coms()[2] != (0x90, 0x00): @@ -346,12 +347,12 @@ class SIM(ISO7816): return None def get_services(self): - ''' + """ self.get_services() -> None reads SIM Service Table at address [0x6F, 0x38] returns list of services allowed / activated - ''' + """ # select DF_GSM for SIM card self.select([0x7F, 0x20]) if self.coms()[2] != (0x90, 0x00): @@ -371,13 +372,13 @@ class SIM(ISO7816): return self.get_services_from_sst(sst['Data']) def read_services(self): - ''' + """ self.read_services() -> None reads SIM Service Table at address [0x6F, 0x38] prints services allowed / activated returns None - ''' + """ serv = self.get_services() for s in serv: print(s) @@ -401,7 +402,7 @@ class SIM(ISO7816): return services def explore_fs(self, filename='sim_fs', depth=True, emul=False): - ''' + """ self.explore_fs(self, filename='sim_fs') -> void filename: file to write in information found depth: depth in recursivity, True=infinite @@ -409,7 +410,7 @@ class SIM(ISO7816): brute force all file addresses from MF recursively (until no more DF are found) write information on existing DF and file in the output file - ''' + """ simfs_entries = MF_FS.keys() if not emul: self.explore_DF([], None, depth) diff --git a/card/USIM.py b/card/USIM.py index 930d761..ce1a62e 100644 --- a/card/USIM.py +++ b/card/USIM.py @@ -132,26 +132,60 @@ USIM_service_table = { 95 : 'Support of UICC access to IMS', 96 : 'Non-Access Stratum configuration by USIM', 97 : 'PWS configuration by USIM', + 98 : 'RFU', + 99 : 'URI support by UICC', + 100: 'Extended EARFCN support', + 101: 'ProSe', + 102: 'USAT Application Pairing', + 103: 'Media Type support', + 104: 'IMS call disconnection cause', + 105: 'URI support for MO SHORT MESSAGE CONTROL', + 106: 'ePDG configuration Information support', + 107: 'ePDG configuration Information configured', + 108: 'ACDC support', + 109: 'Mission Critical Services', + 110: 'ePDG configuration Information for Emergency Service support', + 111: 'ePDG configuration Information for Emergency Service configured', + 112: 'eCall Data over IMS', + 113: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111', + 114: 'From Preferred', + 115: 'IMS configuration data', + 116: 'TV configuration', + 117: '3GPP PS Data Off', + 118: '3GPP PS Data Off Service List', + 119: 'V2X', + 120: 'XCAP Configuration Data', + 121: 'EARFCN list for MTC/NB-IOT UEs', + 122: '5GS Mobility Management Information', + 123: '5G Security Parameters', + 124: 'Subscription identifier privacy support', + 125: 'SUCI calculation by the USIM', + 126: 'UAC Access Identities support', + 127: 'Control plane-based steering of UE in VPLMN', + 128: 'Call control on PDU Session by USIM', + 129: '5GS Operator PLMN List', + 130: 'Support for SUPI of type network specific identifier', } + class USIM(UICC): - ''' + """ defines attributes, methods and facilities for ETSI / 3GPP USIM card check USIM specifications in 3GPP TS 31.102 inherits (eventually overrides) methods and objects from UICC class use self.dbg = 1 or more to print live debugging information - ''' + """ - def __init__(self, atr = None): - ''' + def __init__(self): + """ initializes like an ISO7816-4 card with CLA=0x00 and checks available AID (Application ID) read from EF_DIR initializes on the MF - ''' + """ # initialize like a UICC - ISO7816.__init__(self, atr, CLA=0x00) + ISO7816.__init__(self, CLA=0x00) self.AID = [] if self.dbg >= 2: @@ -209,12 +243,12 @@ class USIM(UICC): return status def get_imsi(self): - ''' + """ get_imsi() -> string(IMSI) reads IMSI value at address [0x6F, 0x07] returns IMSI string on success or None on error - ''' + """ # select IMSI file imsi = self.select([0x6F, 0x07]) if imsi is None: @@ -229,14 +263,14 @@ class USIM(UICC): return None def get_CS_keys(self): - ''' + """ get_CS_keys() -> [KSI, CK, IK] reads CS UMTS keys at address [0x6F, 0x08] returns list of 3 keys, each are list of bytes, on success (or eventually the whole file dict if the format is strange) or None on error - ''' + """ EF_KEYS = self.select( [0x6F, 0x08] ) if self.coms()[2] == (0x90, 0x00): if len(EF_KEYS['Data']) == 33: @@ -251,14 +285,14 @@ class USIM(UICC): return None def get_PS_keys(self): - ''' + """ get_PS_keys() -> [KSI, CK_PS, IK_PS] reads PS UMTS keys at address [0x6F, 0x09] returns list of 3 keys, each are list of bytes, on success (or eventually the whole file dict if the format is strange) or None on error - ''' + """ EF_KEYSPS = self.select( [0x6F, 0x09] ) if self.coms()[2] == (0x90, 0x00): if len(EF_KEYSPS['Data']) == 33: @@ -273,7 +307,7 @@ class USIM(UICC): return None def get_GBA_BP(self): - ''' + """ get_GBA_BP() -> [[RAND, B-TID, KeyLifetime], ...], Length-Value parsing style @@ -282,7 +316,7 @@ class USIM(UICC): returns list of list of bytes on success (or eventually the whole file dict if the format is strange) or None on error - ''' + """ EF_GBABP = self.select( [0x6F, 0xD6] ) if self.coms()[2] == (0x90, 0x00): if len(EF_GBABP['Data']) > 2: @@ -296,7 +330,7 @@ class USIM(UICC): return None def update_GBA_BP(self, RAND, B_TID, key_lifetime): - ''' + """ update_GBA_BP([RAND], [B_TID], [key_lifetime]) -> void (or EF_GBABP file dict if RAND not found) @@ -305,7 +339,7 @@ class USIM(UICC): and updates the file structure with provided B-TID and KeyLifetime returns nothing (or eventually the whole file dict if the RAND is not found) - ''' + """ GBA_BP = self.get_GBA_BP() for i in GBA_BP: if i == RAND: @@ -328,14 +362,14 @@ class USIM(UICC): return GBA_BP def get_GBA_NL(self): - ''' + """ get_GBA_NL() -> [[NAF_ID, B-TID], ...] , TLV parsing style reads EF_GBANL file at address [0x6F, 0xDA], containing NAF_ID and B-TID returns list of list of bytes vector on success (or eventually the whole file dict if the format is strange) or None on error - ''' + """ EF_GBANL = self.select( [0x6F, 0xDA] ) if self.coms()[2] == (0x90, 0x00): if len(EF_GBANL['Data'][0]) > 2: @@ -366,7 +400,7 @@ class USIM(UICC): return None def authenticate(self, RAND=[], AUTN=[], ctx='3G'): - ''' + """ self.authenticate(RAND, AUTN, ctx='3G') -> [key1, key2...], LV parsing style @@ -380,7 +414,7 @@ class USIM(UICC): [RES] or [AUTS] for 'GBA' [RES, Kc] for '2G' or None on error - ''' + """ # prepare input data for authentication if ctx in ('3G', 'VGCS', 'GBA', 'MBMS') and len(RAND) != 16 \ and len(AUTN) != 16: @@ -446,7 +480,7 @@ class USIM(UICC): return None def GBA_derivation(self, NAF_ID=[], IMPI=[]): - ''' + """ self.GBA_derivation(NAF_ID, IMPI) -> [Ks_ext_naf] runs the INTERNAL AUTHENTICATE command in the USIM @@ -464,7 +498,7 @@ class USIM(UICC): or None on error see TS 33.220 for GBA specific formats - ''' + """ # need to run 1st an authenicate command with 'GBA' context, # so to have the required keys in the USIM P2 = 0x84 @@ -486,13 +520,13 @@ class USIM(UICC): return None def get_services(self): - ''' + """ self.get_services() -> None reads USIM Service Table at address [0x6F, 0x38] prints services allowed / activated returns None - ''' + """ # select SST file sst = self.select([0x6F, 0x38]) if self.coms()[2] != (0x90, 0x00): @@ -525,7 +559,7 @@ class USIM(UICC): return services def explore_fs(self, filename='usim_fs', depth=2): - ''' + """ self.explore_fs(self, filename='usim_fs') -> void filename: file to write in information found depth: depth in recursivity, True=infinite @@ -533,7 +567,7 @@ class USIM(UICC): brute force all file addresses from 1st USIM AID with a maximum recursion level (to avoid infinite looping...) write information on existing DF and file in the output file - ''' + """ usimfs_entries = USIM_app_FS.keys() self.explore_DF([], self.AID.index(self.USIM_AID)+1, depth) diff --git a/card/utils.py b/card/utils.py index 4f52ea4..028e918 100644 --- a/card/utils.py +++ b/card/utils.py @@ -23,6 +23,8 @@ with this program; if not, write to the Free Software Foundation, Inc., # being used in smartcard specs # ################################# +import sys + from collections import deque from smartcard.util import toBytes @@ -35,9 +37,39 @@ def log(level, string): # could output to a file # but here, just print() print('[%s] %s' % (log_levels[level], string)) - + + +BER_TAG = { + 1 : 'BOOLEAN', + 2 : 'INTEGER', + 3 : 'BIT STRING', + 4 : 'OCTET STRING', + 5 : 'NULL', + 6 : 'OID', + 7 : 'ObkectDescriptor', + 8 : 'EXTERNAL', + 9 : 'REAL', + 10: 'ENUMERATED', + 11: 'EMBEDDED-PDV', + 12: 'UTF8String', + 13: 'RELATIVE-OID', + 16: 'SEQUENCE', + 17: 'SET', + 19: 'PrintableString', + 22: 'IA5String', + 23: 'UTCTime', + 24: 'GeneralizedTime', + 26: 'VisibleString', + 31: 'DATE', + 32: 'TIME-OF-DAY', + 33: 'DATE-TIME', + 34: 'DURATION', + 35: 'OID-IRI', + 36: 'RELATIVE-OID-IRI' + } + # from python 2.6, format('b') allows to use 0b10010110 notation: -# much convinient +# much convenient def byteToBit(byte): ''' byteToBit(0xAB) -> [1, 0, 1, 0, 1, 0, 1, 1] @@ -59,10 +91,16 @@ def stringToByte(string): converts a string into a list of bytes ''' - bytelist = [] - for c in string: - bytelist.extend( toBytes(c.encode('hex')) ) - return bytelist + if sys.version_info[0] < 3: + bytelist = [] + for c in string: + bytelist.extend( toBytes(c.encode('hex')) ) + return bytelist + else: + if isinstance(string, str): + return list(string.encode('ascii')) + else: + return list(string) # equivalent to the pyscard function "toASCIIString" def byteToString(bytelist): @@ -71,10 +109,13 @@ def byteToString(bytelist): converts a list of bytes into a string ''' - string = '' - for b in bytelist: - string += chr(b) - return string + if sys.version_info[0] < 3: + string = '' + for b in bytelist: + string += chr(b) + return string + else: + return bytes(bytelist) def LV_parser(bytelist): ''' @@ -168,7 +209,7 @@ def first_BERTLV_parser(bytelist): else: Tag_bits = byte0[3:8] - # Tag number calculation + # Tag number calculation Tag_num = 0 for j in range(len(Tag_bits)): Tag_num += Tag_bits[len(Tag_bits)-j-1] * pow(2, j) @@ -178,7 +219,8 @@ def first_BERTLV_parser(bytelist): Len_num = bytelist[i+1] - 0x80 Len = reduce(lambda x,y: (x<<8)+y, bytelist[i+2:i+2+Len_num]) Val = bytelist[i+2+Len_num:i+2+Len_num+Len] - # Length coded with 1 byte + + # Length coded with 1 byte (BER short form) else: Len_num = 1 Len = bytelist[i+1] @@ -204,6 +246,66 @@ def BERTLV_parser(bytelist): bytelist = bytelist[ T[0] + L[0] + L[1] : ] return ret +def BERTLV_extract(bytelist): + ''' + BERTLV_extract([]) -> {} + + parse the input bytes as BERTLV structure recursively until no more + constructed object are present, and returns a corresponding dict of + {tag_value: (tag_complete, data_value)} + ''' + ret = [] + comps = BERTLV_parser(bytelist) + for comp in comps: + if comp[0][1] == 'primitive': + if comp[0][0] == 'universal' and comp[0][2] in BER_TAG: + ret.append( [[comp[0][0], comp[0][2], BER_TAG[comp[0][2]]], + comp[2]] ) + #if comp[0][2] == 6: + # # decode OID + # ret[-1].append( decode_OID(ret[-1][1]) ) + else: + ret.append( [[comp[0][0], comp[0][2]], comp[2]] ) + else: + if comp[0][0] == 'universal' and comp[0][2] in BER_TAG: + ret.append( [[comp[0][0], comp[0][2], BER_TAG[comp[0][2]]], + BERTLV_extract(comp[2])] ) + else: + ret.append( [[comp[0][0], comp[0][2]], + BERTLV_extract(comp[2])] ) + return ret + +def decode_OID(data=[]): + ''' + decode a BER-encoded ASN.1 OID into a string representing the ASN.1 OID + abstract value + ''' + if not data: + return '' + arcs = [] + # decode OID arc values + v = 0 + for b in data: + v <<= 7 + if b&0x80: + v += (b&0x7f) + else: + v += b + arcs.append(v) + v = 0 + if v != 0: + # invalid or incomplete OID + return '' + # + if arcs[0] < 40: + return '0 ' + ' '.join(['%i' % v for v in arcs]) + elif 40 <= arcs[0] < 80: + arcs[0] = arcs[0]-40 + return '1 ' + ' '.join(['%i' % v for v in arcs]) + else: + arcs[0] = arcs[0]-80 + return '2 ' + ' '.join(['%i' % v for v in data]) + def decode_BCD(data=[]): ''' decode_BCD([0x21, 0xFE, 0xA3]) -> '121415310' @@ -216,10 +318,7 @@ def decode_BCD(data=[]): if (B&0x0F) < 10: string += str(B&0x0F) # 2nd digit (4 MSB), can be padding (e.g. 0xF) if (B>>4) < 10: string += str(B>>4) - if len(string) <= 0: - return None - else: - return string + return string def compute_luhn(digit_str=''): ''' @@ -231,7 +330,7 @@ def compute_luhn(digit_str=''): print('you must provide a string of digits') return # append 0 - d = [int(c) for c in digit_str+'0'] + d = [int(c) for c in digit_str + '0'] # sum of odd digits cs = sum(d[-1::-2]) # sum of (sum of digits(even digits * 2)) @@ -246,8 +345,7 @@ def write_dict(dict, fd): ''' write a dict() content to a file descriptor ''' - keys = dict.keys() - keys.sort() + keys = sorted(dict.keys()) fd.write('\n') for k in keys: rec = dict[k] diff --git a/simcard.py b/simcard.py index 4d8b7fe..6f83c44 100644 --- a/simcard.py +++ b/simcard.py @@ -85,7 +85,7 @@ class Simcard(): # Constructor: Create a new simcard object def __init__(self, cardtype = GSM_USIM, atr = None): if cardtype == GSM_USIM: - self.card = USIM(atr) + self.card = USIM() self.usim = True # Detect ISIM / USIM applications