This is merely a historical archive of years 2008-2021, before the migration to mailman3.
A maintained and still updated list archive can be found at https://lists.osmocom.org/hyperkitty/list/gerrit-log@lists.osmocom.org/.
Ludovic Rousseau gerrit-no-reply at lists.osmocom.orgLudovic Rousseau has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-sim-auth/+/20909 ) Change subject: pycodestyle: remove extra space characters ...................................................................... pycodestyle: remove extra space characters ICC.py:707:1: W293 blank line contains whitespace ICC.py:715:57: W291 trailing whitespace etc. Change-Id: Ie8a5fc47775fe7d7fe0e19f7378ffda104fa6112 --- M card/ICC.py M card/SIM.py M card/USIM.py M card/utils.py 4 files changed, 390 insertions(+), 395 deletions(-) git pull ssh://gerrit.osmocom.org:29418/osmo-sim-auth refs/changes/09/20909/1 diff --git a/card/ICC.py b/card/ICC.py index 4a2ba0e..729dbc0 100644 --- a/card/ICC.py +++ b/card/ICC.py @@ -41,23 +41,23 @@ from smartcard.util import toHexString from card.utils import * - + ########################################################### -# ISO7816 class with attributes and methods as defined -# by ISO-7816 part 4 standard for smartcard +# ISO7816 class with attributes and methods as defined +# by ISO-7816 part 4 standard for smartcard ########################################################### 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 - + INS_dic = { 0x04 : 'DEACTIVATE FILE', 0x0C : 'ERASE RECORD(S)', @@ -114,7 +114,7 @@ 0xF2 : 'STATUS', 0xFE : 'TERMINATE CARD USAGE', } - + file_tags = { 0x80 : 'Size', 0x81 : 'Length', @@ -135,13 +135,13 @@ 0xA2 : 'DO Pairs', 0xA5 : 'Proprietary BERTLV', 0xAB : 'Security Attribute expanded', - } - + } + 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 ''' @@ -151,32 +151,32 @@ self.cardservice.connection.connect() self.reader = self.cardservice.connection.getReader() self.ATR = self.cardservice.connection.getATR() - + 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/local/share/pcsc/smartcard_list.txt"): ''' - print smartcard info retrieved from AnswerToReset + 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 - + check also the more complete "parseATR" tool ''' print('\nsmartcard reader: ', self.reader) @@ -198,7 +198,7 @@ ATRfinger = '' j = 1 for i in range(len(smlist)): - if ATRre.match(smlist[i]): + if ATRre.match(smlist[i]): if re.compile(smlist[i][:len(smlist[i])-1]).\ match(toHexString(self.ATR)): while re.compile('\t.{1,}').match(smlist[i+j]): @@ -210,17 +210,17 @@ print("smartcard ATR fingerprint:\n%s" % ATRfinger) else: print("%s file not found" % smlist_file) - + def sw_status(self, 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' + 'command accepted: no further qualification' elif sw1 == 0x61: status = 'normal processing: %i bytes ' \ 'still available' % sw2 elif sw1 == 0x62: @@ -230,7 +230,7 @@ elif sw2 == 0x81: status += ': part of returned data may' \ 'be corrupted' elif sw2 == 0x82: status += ': end of file/record reached ' \ - 'before reading Le bytes' + 'before reading Le bytes' elif sw2 == 0x83: status += ': selected file invalidated' elif sw2 == 0x84: status += ': FCI not formatted' elif sw2 == 0x85: status += ': selected file in termination state' @@ -303,7 +303,7 @@ elif sw1 == 0x6B and sw2 == 0x00: status = 'checking error: '\ 'wrong parameter(s) P1-P2' elif sw1 == 0x6C: status = 'checking error: wrong length Le: ' \ - 'exact length is %s' % toHexString([sw2]) + 'exact length is %s' % toHexString([sw2]) elif sw1 == 0x6D and sw2 == 0x00: status = 'checking error: ' \ 'instruction code not supported or invalid' elif sw1 == 0x6E and sw2 == 0x00: status = 'checking error: ' \ @@ -311,20 +311,20 @@ elif sw1 == 0x6F and sw2 == 0x00: status = 'checking error: ' \ 'no precise diagnosis' return status - + def sr_apdu(self, apdu, force=False): ''' - sr_apdu(apdu=[0x.., 0x.., ...]) -> + sr_apdu(apdu=[0x.., 0x.., ...]) -> list [ string(apdu sent information), string(SW codes interpretation), 2-tuple(sw1, sw2), list(response bytes) ] - + generic function to send apdu, receive and interpret response force: force card reconnection if pyscard transmission fails ''' if force: - try: + try: data, sw1, sw2 = self.cardservice.connection.transmit(apdu) except CardConnectionException: ISO7816.__init__(self, CLA = self.CLA) @@ -332,115 +332,115 @@ else: data, sw1, sw2 = self.cardservice.connection.transmit(apdu) # replaces INS code by strings when available - if apdu[1] in list(self.INS_dic.keys()): + if apdu[1] in list(self.INS_dic.keys()): apdu_name = self.INS_dic[apdu[1]] + ' ' - else: + else: apdu_name = '' sw_stat = self.sw_status(sw1, sw2) return ['%sapdu: %s' % (apdu_name, toHexString(apdu)), 'sw1, sw2: %s - %s' % ( toHexString([sw1, sw2]), sw_stat ), (sw1, sw2), data ] - + def bf_cla(self, start=0, param=[0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00]): ''' - bf_cla( start=int(starting CLA), + bf_cla( start=int(starting CLA), param=list(bytes for selecting file 0x3F, 0x00) ) -> list( CLA which could be supported ) - + tries all classes CLA codes to check the possibly supported ones prints CLA suspected to be supported returns the list of those CLA codes - - WARNING: + + 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) - if ret[2] != (0x6E, 0x00): + if ret[2] != (0x6E, 0x00): print(ret) clist.append(i) return clist - + def bf_ins(self, start=0): ''' - bf_cla( start=int(starting INS) ) + bf_cla( start=int(starting INS) ) -> list( INS which could be supported ) - + tries all instructions INS codes to check the supported ones prints INS suspected to be supported returns the list of those INS codes - - WARNING: + + 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 > 1: + if self.dbg > 1: print('DEBUG: testing %d for INS code with %d CLA code' \ % (i, self.CLA)) ret = self.sr_apdu([self.CLA, i, 0x00, 0x00]) - if ret[2] != (0x6D, 0x00): + if ret[2] != (0x6D, 0x00): print(ret) ilist.append(i) return ilist - + ### # Below is defined a list of standard commands to be used with (U)SIM cards - # They are mainly defined and described in + # They are mainly defined and described in # 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: + if Lc is None: ERASE_BINARY = [self.CLA, 0x0E, P1, P2] - else: + else: ERASE_BINARY = [self.CLA, 0x0E, P1, P2, 0x02] + Data - return self.sr_apdu(ERASE_BINARY) - + 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 @@ -448,11 +448,11 @@ ''' 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 @@ -460,22 +460,22 @@ ''' 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 @@ -483,40 +483,40 @@ ''' 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: + if len(Data) == 0: PUT_DATA = [self.CLA, 0xDA, P1, P2] - elif 1 <= len(Data) <= 255: + elif 1 <= len(Data) <= 255: PUT_DATA = [self.CLA, 0xDA, P1, P2, len(Data)] + Data # should never be the case, however... who wants to try else: PUT_DATA = [self.CLA, 0xDA, P1, P2, 0xFF] + Data[0:255] - return self.sr_apdu(PUT_DATA) - + return self.sr_apdu(PUT_DATA) + 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 @@ -525,107 +525,107 @@ 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: + if len(Data) == 0: VERIFY = [self.CLA, 0x20, 0x00, P2] - elif 1 <= len(Data) <= 255: + elif 1 <= len(Data) <= 255: VERIFY = [self.CLA, 0x20, 0x00, P2, len(Data)] + Data # should never be the case, however... who wants to try - else: + else: VERIFY = [self.CLA, 0x20, 0x00, P2, 0xFF] + Data[0:255] 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 + 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: + if len(Data) == 0: EXTERNAL_AUTHENTICATE = [self.CLA, 0x82, P1, P2] - elif 1 <= len(Data) <= 255: + elif 1 <= len(Data) <= 255: EXTERNAL_AUTHENTICATE = [self.CLA, 0x82, P1, P2, len(Data)] + Data # should never be the case, however... who wants to try - else: + else: EXTERNAL_AUTHENTICATE = [self.CLA, 0x82, P1, P2, 0xFF] + Data[0:255] - return self.sr_apdu(EXTERNAL_AUTHENTICATE) - + return self.sr_apdu(EXTERNAL_AUTHENTICATE) + def GET_CHALLENGE(self): ''' - APDU command to get a challenge for external entity authentication + 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): + if (P1, P2) == (0x00, 0x00): MANAGE_CHANNEL = [self.CLA, 0x70, P1, P2, 0x01] - else: + else: MANAGE_CHANNEL = [self.CLA, 0x70, P1, P2] return self.sr_apdu(MANAGE_CHANNEL) - + def GET_RESPONSE(self, Le=0x01): ''' - APDU command to retrieve data after selection + 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 ENVELOPPE(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: + if len(Data) == 0: ENVELOPPE = [self.CLA, 0xC2, 0x00, 0x00] - elif 1 <= len(Data) <= 255: + elif 1 <= len(Data) <= 255: ENVELOPPE = [self.CLA, 0xC2, 0x00, 0x00, len(Data)] + Data return self.sr_apdu(ENVELOPPE) - + def SEARCH_RECORD(self, P1=0x00, P2=0x00, Data=[]): ''' - APDU command to seach pattern in the current EF file + APDU command to seach pattern in the current EF file with record structure - + P1: record number P2: type of search Data: list of bytes describing a pattern to search for @@ -633,11 +633,11 @@ ''' 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 @@ -645,24 +645,24 @@ ''' DISABLE_CHV = [self.CLA, 0x26, P1, P2, len(Data)] + Data return self.sr_apdu(DISABLE_CHV) - + def UNBLOCK_CHV(self, P2=0x00, Lc=None, Data=[]): ''' APDU command to unblock CHV code (e.g. with PUK for deblocking PIN) - + P2: type of CHV to unblock Lc: Empty or 0x10 Data: if Lc=0x10, UNBLOCK_CHV value and new CHV value to set call sr_apdu method - + TODO: check the exact coding for the Data ''' - if Lc is None: + if Lc is None: UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2] - else: + else: UNBLOCK_CHV = [self.CLA, 0x2C, 0x00, P2, 0x10] + Data - return self.sr_apdu(UNBLOCK_CHV) - + return self.sr_apdu(UNBLOCK_CHV) + ########################## # evolved "macro" method for ISO7816 card # need the "coms" attribute being an apdu_stack() @@ -670,9 +670,9 @@ 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 + interprets the content of some informative bytes for file structure and parsing method... ''' ber = BERTLV_parser( Data ) @@ -681,54 +681,54 @@ if len(ber) > 1: # TODO: implements recursive BER object parsing print('[WNG] more than 1 BER object: %s' % ber) - + # for FCP control structure, precise parsing is done # this structure seems to be the most used for (U)SIM cards - if ber[0][0][2] == 0x2: + if ber[0][0][2] == 0x2: fil = self.parse_FCP( ber[0][2] ) fil['Control'] = 'FCP' return fil - + # for other control structure, DIY fil = {} - if ber[0][0][2] == 0x4: + if ber[0][0][2] == 0x4: fil['Control'] = 'FMD' if self.dbg: print('[WNG] FMD file structure parsing not implemented') - elif ber[0][0][2] == 0xF: + elif ber[0][0][2] == 0xF: fil['Control'] = 'FCI' if self.dbg: print('[WNG] FCI file structure parsing not implemented') - else: + else: fil['Control'] = ber[0][0] if self.dbg: print('[WNG] unknown file structure') fil['Data'] = ber[0][2] - + 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 + 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 while len(toProcess) > 0: - # TODO: seemd full compliancy + # TODO: seemd full compliancy # would require to work with the BERTLV parser... [T, L, V] = first_TLV_parser(toProcess) if self.dbg > 2: - if T in list(self.file_tags.keys()): + if T in list(self.file_tags.keys()): Tag = self.file_tags[T] - else: + else: Tag = T print('[DBG] %s / %s: %s' % (T, Tag, V)) - + # do extra processing here # File ID, DF name, Short file id if T in (0x83, 0x84, 0x88): @@ -739,8 +739,8 @@ fil = self.parse_security_attribute_compact(V, fil) # Security Attributes elif T in (0x86, 0x8B, 0x8E, 0xA0, 0xA1, 0xAB): - fil[self.file_tags[T]] = V - # TODO: no concrete parsing at this time... + fil[self.file_tags[T]] = V + # TODO: no concrete parsing at this time... fil = self.parse_security_attribute(V, fil) # file size or length elif T in (0x80, 0x81): @@ -762,16 +762,16 @@ fil[self.file_tags[T]] = V else: fil[T] = V - + # truncate the data to process and loop if L < 256: toProcess = toProcess[L+2:] else: toProcess = toProcess[L+4:] - - # and return the file + + # and return the file return fil - + @staticmethod def parse_life_cycle(Data, fil): ''' @@ -790,7 +790,7 @@ elif Data[0] >= 16: fil['Life Cycle Status'] = 'proprietary' else: fil['Life Cycle Status'] = 'RFU' return fil - + @staticmethod def parse_file_descriptor(Data, fil): ''' @@ -821,21 +821,21 @@ # type bits b4 to b6 if fd_type == 0: fil['Type'] = 'EF working' elif fd_type == 1: fil['Type'] = 'EF internal' - elif fd_type == 7: + elif fd_type == 7: fil['Type'] = 'DF' if fd_struct == 1: fil['Structure'] = 'BER-TLV' elif fd_struct == 2: fil['Structure'] = 'TLV' else: fil['Type'] = 'EF proprietary' - - # for linear and cyclic EF: - # the following is convenient for UICC, + + # for linear and cyclic EF: + # the following is convenient for UICC, # but looks not fully conform to ISO standard # see coding convention in ISO 7816-4 Table 87 - if len(Data) == 5: + if len(Data) == 5: fil['Record Length'], fil['Record Number'] = Data[3], Data[4] - + return fil - + @staticmethod def parse_proprietary(Data, fil): ''' @@ -856,11 +856,11 @@ } while len(Data) > 0: [T, L, V] = first_TLV_parser( Data ) - if T in list(propr_tags.keys()): + if T in list(propr_tags.keys()): fil[propr_tags[T]] = V Data = Data[L+2:] return fil - + @staticmethod def parse_security_attribute_compact(Data, fil): ''' @@ -872,7 +872,7 @@ AM = Data[0] SC = Data[1:] sec = '#' - + if 'Type' in list(fil.keys()): # DF parsing if fil['Type'] == 'DF': @@ -894,7 +894,7 @@ if AM & 0b00000100: sec += ' WRITE / APPEND #' if AM & 0b00000010: sec += ' UPDATE / ERASE #' if AM & 0b00000001: sec += ' READ / SEARCH #' - + # loop on SC: for cond in SC: if cond == 0 : sec += ' Always #' @@ -906,28 +906,28 @@ if cond & 0b01000000: sec += ' secure messaging #' if cond & 0b00100000: sec += ' external authentication #' if cond & 0b00010000: sec += ' user authentication #' - + #file['Security Attributes raw'] = Data fil['Security Attributes'] = sec return fil - + @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 #if self.dbg: # print '[DBG] parse_security_attribute() not implemented' return fil - + 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 + with "Data" key and corresponding - list of bytes for EF transparent - list of list of bytes for cyclic or linear EF ''' @@ -935,11 +935,11 @@ if fil['Structure'] == 'transparent': self.coms.push( self.READ_BINARY(Le=fil['Size']) ) if self.coms()[2] != (0x90, 0x00): - if self.dbg > 1: + if self.dbg > 1: print('[DBG] %s' % self.coms()) return fil fil['Data'] = self.coms()[3] - + # read EF cyclic / linear all records data elif fil['Structure'] != 'transparent': fil['Data'] = [] @@ -949,7 +949,7 @@ self.coms.push( self.READ_RECORD(P1=i+1, P2=0x04, \ Le=fil['Record Length']) ) if self.coms()[2] != (0x90, 0x00): - # should mean there is an issue + # should mean there is an issue # somewhere in the file parsing process if self.dbg: print('[WNG] error in iterating the RECORD parsing at' \ @@ -958,18 +958,18 @@ if self.coms()[3][1:] == len(self.coms()[3][1:]) * [255]: # record is empty, contains padding only pass - else: + else: fil['Data'].append(self.coms()[3]) - - # return the [Data] for transparent or + + # return the [Data] for transparent or # [[Record1],[Record2]...] for cyclic / linear return fil - + def select(self, Data=[0x3F, 0x00], typ="fid", with_length=True): ''' - self.select(Data=[0x.., 0x..], typ="fid", with_length=True) + self.select(Data=[0x.., 0x..], typ="fid", with_length=True) -> dict(file) on success, None on error - + selects the file if error, returns None if processing correct: gets response with info on the file @@ -977,89 +977,89 @@ works in USIM fashion else returns the data dictionnary: check parse_file_(U)SIM methods last apdu available from the attribute self.coms - + different types of file selection are possible: - "fid": select by file id, only the direct child or + "fid": select by file id, only the direct child or parent files of the last selected MF / DF / ADF "pmf": select by path from MF - "pdf": select by path from last selected MF / DF / ADF + "pdf": select by path from last selected MF / DF / ADF (or relative path) "aid": select by ADF (Application) name ''' # get the UICC trigger is_UICC = isinstance(self, UICC) - + # handle type of selection: if typ == "pmf": P1 = 0x08 elif typ == "pdf": P1 = 0x09 elif typ == "aid": P1 = 0x04 # the case of selection by "fid": - else: P1 = 0x00 - + else: P1 = 0x00 + # for UICC instance # ask the return of the FCP template for the selected file: if is_UICC: P2 = 0x04 else: P2 = 0x00 - + # used to get back to MF without getting MF attributes: - if len(Data) == 0: + if len(Data) == 0: P1, P2 = 0x00, 0x0C - + # select file and check SW; if error, returns None, else get response self.coms.push(self.SELECT_FILE(P1=P1, P2=P2, Data=Data, \ with_length=with_length)) - + # different SW codes for UICC and old ISO card (e.g. SIM) if is_UICC and self.coms()[2][0] != 0x61 \ or not is_UICC and self.coms()[2][0] != 0x9F: - if self.dbg > 1: + if self.dbg > 1: print('[DBG] %s' % self.coms()) return None - - # get response and check SW: + + # get response and check SW: # if error, return None, else parse file info self.coms.push(self.GET_RESPONSE(Le=self.coms()[2][1])) if self.coms()[2] != (0x90, 0x00): - if self.dbg > 1: + if self.dbg > 1: print('[DBG] %s' % self.coms()) return None - + data = self.coms()[3] # take the `parse_file()' method from the instance: # ISO7816, UICC or SIM fil = self.parse_file(data) if fil['Type'][0:2] == 'EF': fil = self.read_EF(fil) - - # finally returns the whole file dictionnary, + + # finally returns the whole file dictionnary, # containing the ['Data'] key for EF file return fil - + # ############### # TODO: # improve all of the following... ############### - + def flat_files_bf(self, path=[], under_AID=0, \ hi_addr=(0, 0xff), lo_addr=(0, 0xff)): ''' flat_files_bf(self, path=[], under_AID=0, \ hi_addr=(0, 0xff), lo_addr=(0, 0xff)) -> list(files), list(DF_to_explore) - + path: path of the DF under MF or AID to brute force under_AID: if > 0, select the AID number to init the brute force only available for UICC instance hi_addr: 8 MSB of the file address to brute force lo_addr: 8 LSB of the file address to brute force with_select_length: use the length parameter with SELECT instruction - + brute force file addresses of direct child under a given DF get information on existing files, and discovered DF - + WARNING: not very tested yet... ''' # init return variables @@ -1068,7 +1068,7 @@ MF, sel_type = [0x3F, 0x00], 'fid' if isinstance(self, UICC): sel_type = 'pdf' - + # start by selecting MF try: r = self.select(MF) @@ -1078,7 +1078,7 @@ if r == None: print('[ERR] MF not found!') return - + #if needed, select AID if isinstance(self, UICC) and under_AID: try: @@ -1090,7 +1090,7 @@ if r == None: print('[ERR] AID not found') return - + # place on the DF path to bf # select it by 'path from last selected DF' if len(path) > 0: @@ -1102,7 +1102,7 @@ if path_init == None: print('[ERR] path not found: %s' % path) return - + # Dany'style programming def reinit(): self.select(MF) @@ -1110,7 +1110,7 @@ self.select_by_aid(under_AID) if len(path) > 0: self.select(path, sel_type) - + # loop over the address space to brute force files i, j = 0, 0 for i in range(hi_addr[0], hi_addr[1]): @@ -1135,19 +1135,19 @@ reinit() if 'Absolut Path' in list(fil.keys()): DF_to_explore.append(fil['Absolut Path']) - + # re-initialize at MF and return self.select(MF) return FS, DF_to_explore - + def init_FS(self): self.FS = [] - + def recu_files_bf(self, path=[], under_AID=0): ''' recu_files_bf(self, path=[], under_AID=0) -> void - + fills self.FS attribute with all files and DF discovered recursively ''' @@ -1159,13 +1159,13 @@ '[ERR] FS not initialized: %s' % type(self.FS) return DF = ret[1] - + # recursive method call # DF contains absolut path for addr in DF: print('[DBG] path: %s' % addr) self.recu_files_bf(path=addr, under_AID=under_AID) - + @staticmethod def __write_dict(dict, fd): keys = list(dict.keys()) @@ -1173,31 +1173,31 @@ fd.write('\n') for k in keys: fd.write('%s: %s\n' % (k, dict[k])) - - + + def scan_fs(self, filename='card_fs', stdout=False): ''' bf_files_under_MF(self, output='card_fs', stdout=True) -> void - + filename: file to write found information in stdout: print information on stdout too - + brute force all file addresses from MF and found AID recursively (until no more DF are found) - write information on existing file on the output, - + write information on existing file on the output, + WARNING: not very tested either... ''' fd = open(filename, 'w') - + self.init_FS() self.recu_files_bf() fd.write('\n### MF ###\n') for f in self.FS: self.__write_dict(f, fd) fd.write('\n') - + # TODO: loop that #self.init_FS() #self.recu_files_bf(under_AID=1) @@ -1205,7 +1205,7 @@ #for f in self.FS: # self.__write_dict(f, fd) # fd.write('\n') - + fd.close() # @@ -1217,7 +1217,7 @@ ''' 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 ''' @@ -1254,7 +1254,7 @@ (0xFF, 0x44): 'United Kingdom', (0xFF, 0x49): 'Germany', } - + pin_status = { 0x01 : "PIN Appl 1", 0x02 : "PIN Appl 2", @@ -1286,7 +1286,7 @@ 0x8D : "ADM9", 0x8E : "ADM10", } - + files = [ ([0x3F, 0x00], 'MF', 'MF'), ([0x2F, 0x00], 'EF', 'EF_DIR'), @@ -1308,48 +1308,48 @@ ([0x7F, 0x90], 'DF', 'DF_TETRA'), ([0x7F, 0x31], 'DF', 'DF_iDEN'), ] - + def __init__(self): ''' initializes like an ISO7816-4 card with CLA=0x00 and check available AID (Application ID) read from EF_DIR - + initializes on the MF ''' ISO7816.__init__(self, CLA=0x00) self.AID = [] - + if self.dbg: print('[DBG] type definition: %s' % type(self)) print('[DBG] CLA definition: %s' % hex(self.CLA)) #print '[DBG] EF_DIR file selection and reading...' - + def parse_file(self, Data=[]): ''' parse_file(Data=[0x12, 0x34, 0x56, 0x89]) -> dict(file) mainly based on the ISO7816 parsing style - + parses a list of bytes returned when selecting a file - interprets the content of some informative bytes for right accesses, + 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) - + # Then UICC extra attributes parsing if 0xC6 in list(fil.keys()): fil = self.parse_pin_status(fil[0xC6], fil) del fil[0xC6] - + if 'File Identifier' in list(fil.keys()): for ref in self.files: if fil['File Identifier'] == ref[0]: fil['Name'] = ref[2] - - # return the enriched file + + # return the enriched file return fil - + @staticmethod def parse_pin_status(Data, fil): ''' @@ -1364,106 +1364,106 @@ [T, L, V] = first_TLV_parser(Data) assert( T in (0x83, 0x95) ) if T == 0x95: # PIN usage - if (V[0] << 7) & 1: + if (V[0] << 7) & 1: PIN_status += '#use verification / encipherment ' \ '/ external authentication: ' - elif (V[0] << 6) & 1: + elif (V[0] << 6) & 1: PIN_status += '#use computation / decipherment ' \ '/ internal authentication: ' - elif (V[0] << 5) & 1: + elif (V[0] << 5) & 1: PIN_status += '#use SM response: ' - elif (V[0] << 4) & 1: + elif (V[0] << 4) & 1: PIN_status += '#use SM command: ' - elif (V[0] << 3) & 1: + elif (V[0] << 3) & 1: PIN_status += '#use PIN verification: ' - elif (V[0] << 3) & 1: + elif (V[0] << 3) & 1: PIN_status += '#use biometric user verification: ' - elif V[0] == 0: + elif V[0] == 0: PIN_status += '#verification not required: ' elif T == 0x83: # PIN status if len(PIN_status) == 0: PIN_status = '#' - if 0x00 < V[0] < 0x12 or 0x81 <= V[0] < 0x90: + if 0x00 < V[0] < 0x12 or 0x81 <= V[0] < 0x90: PIN_status += UICC.pin_status[V[0]] + '#' elif 0x12 <= V[0] < 0x1E: PIN_status += 'RFU (Global)#' elif 0x90 <= V[0] < 0x9F: PIN_status += 'RFU (Local)#' - else: + else: PIN_status += '#' - #if self.dbg >= 2: + #if self.dbg >= 2: # print '[DBG] %s: %s; PIN status: %s' % (T, V, PIN_status) Data = Data[L+2:] fil['PIN Status'] = PIN_status return fil - + def get_AID(self): ''' - checks EF_DIR at the MF level, + 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(Data=[]) - + # EF_DIR is at the MF level and contains Application ID: EF_DIR = self.select([0x2F, 0x00], typ='pmf') - if self.dbg: + if self.dbg: print('[DBG] EF_DIR: %s' % EF_DIR) - if EF_DIR is None: + if EF_DIR is None: return None - + # EF_DIR is an EF with linear fixed structure: contains records: for rec in EF_DIR['Data']: # check for a (new) AID: if (rec[0], rec[2]) == (0x61, 0x4F) and len(rec) > 6 \ and rec[4:4+rec[3]] not in self.AID: self.AID.append( rec[4:4+rec[3]] ) - + i = 1 for aid in self.AID: aid_rid = tuple(aid[0:5]) aid_app = tuple(aid[5:7]) aid_country = tuple(aid[7:9]) aid_provider = tuple(aid[9:11]) - + # get AID application code, depending on SDO... if aid_rid == (0xA0, 0x00, 0x00, 0x00, 0x09) \ - and aid_app in list(self.ETSI_AID_app_code.keys()): + and aid_app in list(self.ETSI_AID_app_code.keys()): aid_app = self.ETSI_AID_app_code[aid_app] if aid_rid == (0xA0, 0x00, 0x00, 0x00, 0x87) \ - and aid_app in list(self.GPP_AID_app_code.keys()): + and aid_app in list(self.GPP_AID_app_code.keys()): aid_app = self.GPP_AID_app_code[aid_app] if aid_rid == (0xA0, 0x00, 0x00, 0x03, 0x43) \ - and aid_app in list(self.GPP2_AID_app_code.keys()): + and aid_app in list(self.GPP2_AID_app_code.keys()): aid_app = self.GPP2_AID_app_code[aid_app] # get AID responsible SDO and country if aid_rid in list(self.AID_RID.keys()): aid_rid = self.AID_RID[aid_rid] - if aid_country in list(self.AID_country_code.keys()): + if aid_country in list(self.AID_country_code.keys()): aid_country = self.AID_country_code[aid_country] - + print('found [AID %s] %s || %s || %s || %s || %s' \ % (i, aid_rid, aid_app, aid_country, \ aid_provider, tuple(aid[11:]) )) i += 1 - + def get_ICCID(self): ''' - check EF_ICCID at the MF level, - and returnq the ASCII value of the ICCID + 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(Data=[]) - + # EF_ICCID is at the MF level and contains Application ID: EF_ICCID = self.select([0x2F, 0xE2], typ='pmf') - if self.dbg: + if self.dbg: print('[DBG] EF_ICCID: %s' % EF_ICCID) - if EF_ICCID is None: + if EF_ICCID is None: return None return decode_BCD( EF_ICCID['Data'] ) - + def select_by_name(self, name=''): ''' AID selection by name taken from UICC.files @@ -1471,13 +1471,10 @@ for i in range(len(self.files)): if name == self.files[i][2]: return self.select( self.files[i][0], 'pmf' ) - + def select_by_aid(self, aid_num=1): ''' AID selection by index ''' if len(self.AID) != 0: return self.select(self.AID[aid_num-1], 'aid') - - - diff --git a/card/SIM.py b/card/SIM.py index 78864a7..555f0df 100644 --- a/card/SIM.py +++ b/card/SIM.py @@ -36,11 +36,11 @@ ''' 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): ''' initialize like an ISO7816-4 card with CLA=0xA0 @@ -50,7 +50,7 @@ if self.dbg: print('[DBG] type definition: %s' % type(self)) print('[DBG] CLA definition: %s' % hex(self.CLA)) - + self.caller = { 'KC' : self.get_Kc, 'IMSI' : self.get_imsi, @@ -63,12 +63,12 @@ 'MSISDN' : self.get_msisdn, 'SMSP' : self.get_smsp, } - + def sw_status(self, sw1, sw2): ''' sw_status(sw1=int, sw2=int) -> string - - extends SW status bytes interpretation from ISO7816 + + extends SW status bytes interpretation from ISO7816 with ETSI / 3GPP SW codes helps to speak with the smartcard! ''' @@ -109,7 +109,7 @@ 'application specific' elif sw2 == 0x63: status += ': security session expired' return status - + def verify_pin(self, pin='', pin_type=1): ''' verify CHV1 (PIN code) or CHV2 with VERIFY APDU command @@ -119,14 +119,14 @@ len(pin) == 4 and 0 <= int(pin) < 10000: PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF] self.coms.push( self.VERIFY(P2=pin_type, Data=PIN) ) - else: - if self.dbg: + else: + if self.dbg: print('[WNG] bad 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 + 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 ''' @@ -135,22 +135,22 @@ PIN = [ord(i) for i in pin] + [0xFF, 0xFF, 0xFF, 0xFF] self.coms.push( self.DISABLE_CHV(P2=pin_type, Data=PIN) ) else: - if self.dbg: + if self.dbg: print('[WNG] bad 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! - unblock CHV1 (PIN code) or CHV2 with UNBLOCK_CHV APDU command + unblock CHV1 (PIN code) or CHV2 with UNBLOCK_CHV APDU command and set 0000 value for new PIN call ISO7816 UNBLOCK_CHV method ''' print('not correctly implemented') return - #if pin_type == 1: + #if pin_type == 1: # pin_type = 0 if pin_type in [0, 2] and type(unblock_pin) is str and \ len(unblock_pin) == 4 and 0 <= int(unblock_pin) < 10000: @@ -159,16 +159,16 @@ Data=UNBL_PIN + \ [0x30, 0x30, 0x30, 0x30, 0xFF, 0xFF, 0xFF, 0xFF]) ) else: - if self.dbg: + if self.dbg: print('[WNG] bad parameters') #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, + interprets the content of some informative bytes for right accesses, type / format of file... see TS 51.011 works over the SIM file structure ''' @@ -193,10 +193,10 @@ fil['unblock_CHV2'] = ('not initialized','initialized')\ [(Data[21] & 0x80) // 0x80]\ + ': %d attempts remain' % (Data[21] & 0x0F) - if len(Data) > 23: + if len(Data) > 23: fil['Adm'] = Data[23:] elif fil['Type'] == 'EF': - cond = ('ALW', 'CHV1', 'CHV2', 'RFU', 'ADM_4', 'ADM_5', + cond = ('ALW', 'CHV1', 'CHV2', 'RFU', 'ADM_4', 'ADM_5', 'ADM_6', 'ADM_7', 'ADM_8', 'ADM_9', 'ADM_A', 'ADM_B', 'ADM_C', 'ADM_D', 'ADM_E', 'NEW') fil['UPDATE'] = cond[Data[8] & 0x0F] @@ -204,77 +204,77 @@ fil['INCREASE'] = cond[Data[9] >> 4] fil['INVALIDATE'] = cond[Data[10] & 0x0F] fil['REHABILITATE'] = cond[Data[10] >> 4] - fil['Status'] = ('not read/updatable when invalidated', + fil['Status'] = ('not read/updatable when invalidated', 'read/updatable when invalidated')\ [byteToBit(Data[11])[5]] \ + (': invalidated',': not invalidated')\ [byteToBit(Data[11])[7]] fil['Structure'] = ('transparent', 'linear fixed', '', 'cyclic')\ [Data[13]] - if fil['Structure'] == 'cyclic': + if fil['Structure'] == 'cyclic': fil['INCREASE'] = byteToBit(Data[7])[1] - if len(Data) > 14: + if len(Data) > 14: fil['Record Length'] = Data[14] 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 Kc : list of bytes, length 8 - - run GSM authentication algorithm: + + run GSM authentication algorithm: accepts any kind of RAND (old GSM fashion) feed with RAND 16 bytes value return a list with SRES and Kc, or None on error ''' if len(RAND) != 16: - if self.dbg: + if self.dbg: print('[WNG] needs a 16 bytes input RAND value') return None # select DF_GSM directory self.select([0x7F, 0x20]) - if self.coms()[2] != (0x90, 0x00): - if self.dbg: + if self.coms()[2] != (0x90, 0x00): + if self.dbg: print('[DBG] %s' % self.coms()) return None # run authentication self.coms.push(self.INTERNAL_AUTHENTICATE(P1=0x00, P2=0x00, Data=RAND)) if self.coms()[2][0] != 0x9F: - if self.dbg: + if self.dbg: print('[DBG] %s' % self.coms()) return None # get authentication response self.coms.push(self.GET_RESPONSE(Le=self.coms()[2][1])) if self.coms()[2] != (0x90, 0x00): - if self.dbg: + if self.dbg: print('[DBG] %s' % self.coms()) return None SRES, Kc = self.coms()[3][0:4], self.coms()[3][4:] 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): - if self.dbg: + if self.coms()[2] != (0x90, 0x00): + if self.dbg: print('[DBG] %s' % self.coms()) return None - + # select IMSI file imsi = self.select([0x6F, 0x07]) - if self.coms()[2] != (0x90, 0x00): - if self.dbg: + if self.coms()[2] != (0x90, 0x00): + if self.dbg: print('[DBG] %s' % self.coms()) return None - + # and parse the received data into the IMSI structure if 'Data' in list(imsi.keys()) and len(imsi['Data']) == 9: @@ -282,9 +282,9 @@ print("[DBG] International Mobile Subscriber Identity (IMSI): %s " % decode_BCD(imsi['Data'])[3:]) return decode_BCD(imsi['Data'])[3:] - + # if issue with the content of the DF_IMSI file - if self.dbg: + if self.dbg: print('[DBG] %s' % self.coms()) return None @@ -313,7 +313,7 @@ return Kc['Data'] else: return None - + # EF loci contains location information # This conatins TMSI, LAI, TMSI TIME, and Location update status # and prints the information @@ -590,4 +590,3 @@ return iccid else: return None - diff --git a/card/USIM.py b/card/USIM.py index fa20d1a..45c2a3e 100644 --- a/card/USIM.py +++ b/card/USIM.py @@ -37,16 +37,16 @@ ''' 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): ''' 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 @@ -55,7 +55,7 @@ if self.dbg: print('[DBG] type definition: %s' % type(self)) print('[DBG] CLA definition: %s' % hex(self.CLA)) - + # USIM selection from AID print('[+] UICC AID found:') self.get_AID() @@ -63,37 +63,37 @@ if tuple(aid[0:5]) == (0xA0, 0x00, 0x00, 0x00, 0x87) \ and tuple(aid[5:7]) == (0x10, 0x02) : usim = self.select( Data=aid, typ='aid') - if usim is None: + if usim is None: print('[+] USIM AID selection failed') - else: + else: print('[+] USIM AID selection succeeded\n') - + 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: + if imsi is None: return None # and parse the received data into the IMSI structure if 'Data' in list(imsi.keys()) and len(imsi['Data']) == 9: return decode_BCD(imsi['Data'])[3:] - + # if issue with the content of the DF_IMSI file - if self.dbg: + if self.dbg: print('[DBG] %s' % self.coms()) 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 + 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 ''' @@ -105,39 +105,39 @@ EF_KEYS['Data'][17:33]) print('[+] Successful CS keys selection: Get [KSI, CK, IK]') return [KSI, CK, IK] - else: + else: return EF_KEYS 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 + 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: - KSI, CK, IK = ( EF_KEYSPS['Data'][0:1], - EF_KEYSPS['Data'][1:17], + KSI, CK, IK = ( EF_KEYSPS['Data'][0:1], + EF_KEYSPS['Data'][1:17], EF_KEYSPS['Data'][17:33] ) print('[+] Successful PS keys selection: Get [KSI, CK, IK]') return [KSI, CK, IK] - else: + else: return EF_KEYSPS return None - + def get_GBA_BP(self): ''' - get_GBA_BP() -> [[RAND, B-TID, KeyLifetime], ...], + get_GBA_BP() -> [[RAND, B-TID, KeyLifetime], ...], Length-Value parsing style - - reads EF_GBABP file at address [0x6F, 0xD6], + + reads EF_GBABP file at address [0x6F, 0xD6], containing RAND and associated B-TID and KeyLifetime - returns list of list of bytes on success + returns list of list of bytes on success (or eventually the whole file dict if the format is strange) or None on error ''' @@ -149,17 +149,17 @@ '[RAND, B-TID, KeyLifetime]') #return (RAND, B_TID, Lifetime) return LV_parser( EF_GBABP['Data'] ) - else: + else: return EF_GBABP return None - + def update_GBA_BP(self, RAND, B_TID, key_lifetime): ''' - update_GBA_BP([RAND], [B_TID], [key_lifetime]) + update_GBA_BP([RAND], [B_TID], [key_lifetime]) -> void (or EF_GBABP file dict if RAND not found) - + reads EF_GBABP file at address [0x6F, 0xD6], - checks if RAND provided is referenced, + checks if RAND provided is referenced, 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) @@ -172,35 +172,35 @@ self.coms.push( self.UPDATE_BINARY( P2=len(RAND)+1, Data=[len(B_TID)] + B_TID + \ [len(key_lifetime)] + key_lifetime )) - if self.dbg > 1: + if self.dbg > 1: print('[DBG] %s' % self.coms()) if self.coms()[2] == 0x90 and self.dbg: print('[+] Successful GBA_BP update with B-TID ' \ 'and key lifetime') - if self.dbg > 2: + if self.dbg > 2: print('[DBG] new value of EF_GBA_BP:\n%s' \ % self.get_GBA_BP()) else: - if self.dbg: + if self.dbg: print('[+] RAND not found in GBA_BP') 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 + 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: - # This is Tag-Length-Value parsing, + # This is Tag-Length-Value parsing, # with 0x80 for NAF_ID and 0x81 for B-TID values = [] - + for rec in EF_GBANL['Data']: NAF_ID, B_TID = [], [] while len(rec) > 0: @@ -209,26 +209,26 @@ rec = rec[ tlv[1]+4 : ] else: rec = rec[ tlv[1]+2 : ] - if tlv[0] == 0x80: + if tlv[0] == 0x80: NAF_ID = tlv[2] - elif tlv[0] == 0x81: + elif tlv[0] == 0x81: B_TID = tlv[2] values.append( [NAF_ID, B_TID] ) - + print('[+] Successful GBA_NL selection: ' \ 'Get list of [NAF_ID, B-TID]') #return (NAF_ID, B_TID) return values - else: + else: return EF_GBANL return None - + def authenticate(self, RAND=[], AUTN=[], ctx='3G'): ''' - self.authenticate(RAND, AUTN, ctx='3G') -> [key1, key2...], + self.authenticate(RAND, AUTN, ctx='3G') -> [key1, key2...], LV parsing style - - runs the INTERNAL AUTHENTICATE command in the USIM + + runs the INTERNAL AUTHENTICATE command in the USIM with the right context: ctx = '2G', '3G', 'GBA' ('MBMS' or other not supported at this time) RAND and AUTN are list of bytes; for '2G' context, AUTN is not used @@ -241,10 +241,10 @@ ''' # prepare input data for authentication if ctx in ('3G', 'VGCS', 'GBA', 'MBMS') and len(RAND) != 16 \ - and len(AUTN) != 16: + and len(AUTN) != 16: print('[ERR] missing parameters or wrong length') return None - + inp = [] if ctx == '3G': P2 = 0x81 @@ -255,27 +255,27 @@ elif ctx == 'MBMS': print('[+] Not implemented. Exit.') return None - elif ctx == 'GBA': + elif ctx == 'GBA': P2 = 0x84 inp = [0xDD] inp.extend( [len(RAND)] + RAND + [len(AUTN)] + AUTN ) - if ctx not in ['3G', 'VGCS', 'MBMS', 'GBA']: - # and also, if ctx == '2G'... the safe way + if ctx not in ['3G', 'VGCS', 'MBMS', 'GBA']: + # and also, if ctx == '2G'... the safe way # to avoid desynchronizing our USIM counter P2 = 0x80 - if len(RAND) != 16: + if len(RAND) != 16: print('[ERR] RAND has wrong length (%d, should be 16)' % len(RAND)) return None # override input value for 2G authent inp = [len(RAND)] + RAND - + self.coms.push( self.INTERNAL_AUTHENTICATE(P2=P2, Data=inp) ) if self.coms()[2][0] in (0x9F, 0x61): self.coms.push( self.GET_RESPONSE(Le=self.coms()[2][1]) ) if self.coms()[2] == (0x90, 0x00): val = self.coms()[3] if P2 == 0x80: - if self.dbg: + if self.dbg: print('[+] Successful 2G authentication. Get [RES, Kc]') values = LV_parser(val) # returned values are (RES, Kc) @@ -294,65 +294,65 @@ # returned values can be (RES, CK, IK) or (RES, CK, IK, Kc) return values elif val[0] == 0xDC: - if self.dbg: + if self.dbg: print('[+] Synchronization failure. Get [AUTS]') values = LV_parser(val[1:]) return values elif self.dbg: print('[+] response: %s' % hex(val)) #else: - if self.dbg: + if self.dbg: print('[+] authentication error: %s' % self.coms()) 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 + + runs the INTERNAL AUTHENTICATE command in the USIM with the GBA derivation context: NAF_ID is a list of bytes (use stringToByte()) - "NAF domain name"||"security protocol id", + "NAF domain name"||"security protocol id", eg: "application.org"||"0x010001000a" (> TLS with RSA and SHA) IMPI is a list of bytes "IMSI at ims.mncXXX.mccYYY.3gppnetwork.org" if no IMS IMPI - is specifically defined in the USIM + is specifically defined in the USIM returns a list with GBA ext key (list of bytes) computed in the USIM: [Ks_ext_naf] - Ks_int_naf remains available in the USIM + Ks_int_naf remains available in the USIM for further GBA_U key derivation or None on error - + see TS 33.220 for GBA specific formats ''' - # need to run 1st an authenicate command with 'GBA' context, + # need to run 1st an authenicate command with 'GBA' context, # so to have the required keys in the USIM P2 = 0x84 inp = [0xDE] + [len(NAF_ID)] + NAF_ID + [len(IMPI)] + IMPI - + self.coms.push( self.INTERNAL_AUTHENTICATE(P2=P2, Data=inp) ) if self.coms()[2][0] in (0x9F, 0x61): self.coms.push( self.GET_RESPONSE(Le=self.coms()[2][1]) ) if self.coms()[2] == (0x90, 0x00): val = self.coms()[3] if val[0] == 0xDB: # not adapted to 2G context with Kc, RES - if self.dbg: + if self.dbg: print('[+] Successful GBA derivation. Get [Ks_EXT_NAF]') values = LV_parser(val[1:]) return values - if self.dbg: + if self.dbg: print('[DBG] authentication failure: %s' % self.coms()) return None - - def bf_FS_from_init( self, filename='bf_USIM', file_dict=USIM_app_FS, + + def bf_FS_from_init( self, filename='bf_USIM', file_dict=USIM_app_FS, init_method='select_by_aid', init_args=[1] ): ''' bruteforces the USIM filesystem at the application initialization level: thanks to UICC.select_by_aid(1) could be used another way... - stores the result in the file passed in argument + stores the result in the file passed in argument (file will be overwritten) - + TODO: does not manage file recursivity (when entering DF) only scan 1st level files ''' @@ -360,7 +360,7 @@ # loop on all possible addresses for i in range(0x00, 0xff): for j in range(0x00, 0xff): - # here "ret" is useless, + # here "ret" is useless, # but calling the method places the smartcard # at the right address (by default: 1st AID) ret = getattr(self, init_method)(*init_args) @@ -387,10 +387,10 @@ fd.write('file exists at address: %s %s\n' \ % (hex(i)[2:], hex(j)[2:])) fd.write('%s\n' % self.coms()[1]) - - if self.dbg and (i % 0x10 == 0 and j % 0x40 == 0): + + if self.dbg and (i % 0x10 == 0 and j % 0x40 == 0): print('[-] going over address %s %s' % (i, j)) - + fd.write('\n') fd.close() diff --git a/card/utils.py b/card/utils.py index 9a04dcf..56d70fc 100644 --- a/card/utils.py +++ b/card/utils.py @@ -26,12 +26,12 @@ from smartcard.util import toBytes, toHexString, PACK import sys -# from python 2.6, format('b') allows to use 0b10010110 notation: +# from python 2.6, format('b') allows to use 0b10010110 notation: # much convinient def byteToBit(byte): ''' byteToBit(0xAB) -> [1, 0, 1, 0, 1, 0, 1, 1] - + converts a byte integer value into a list of bits ''' bit = [0, 0, 0, 0, 0, 0, 0, 0] @@ -46,7 +46,7 @@ def stringToByte(string): ''' stringToByte('test') -> [116, 101, 115, 116] - + converts a string into a list of bytes ''' bytelist = [] @@ -62,7 +62,7 @@ def byteToString(bytelist): ''' byteToString([116, 101, 115, 116]) -> 'test' - + converts a list of bytes into a string ''' string = '' @@ -82,7 +82,7 @@ def LV_parser(bytelist): ''' LV_parser([0x02, 0xAB, 0xCD, 0x01, 0x12, 0x34]) -> [[171, 205], [18], []] - + parses Length-Value records in a list of bytes returns a list of list of bytes length coded on 1 byte @@ -97,8 +97,8 @@ def first_TLV_parser(bytelist): ''' first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205]) - - parses first TLV format record in a list of bytelist + + parses first TLV format record in a list of bytelist returns a 3-Tuple: Tag, Length, Value Value is a list of bytes parsing of length is ETSI'style 101.220 @@ -115,7 +115,7 @@ def TLV_parser(bytelist): ''' TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...] - + loops on the input list of bytes with the "first_TLV_parser()" function returns a list of 3-Tuples ''' @@ -127,17 +127,17 @@ break ret.append( (T, L, V) ) # need to manage length of L - if L > 0xFE: + if L > 0xFE: bytelist = bytelist[ L+4 : ] - else: + else: bytelist = bytelist[ L+2 : ] return ret def first_BERTLV_parser(bytelist): ''' - first_BERTLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) + first_BERTLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> ([1, 'contextual', 'constructed', 10], [1, 2], [171, 205]) - + parses first BER-TLV format record in a list of bytes returns a 3-Tuple: Tag, Length, Value Tag: [Tag class, Tag DO, Tag number] @@ -170,12 +170,12 @@ # Tag coded with 1 byte 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) - + # Length coded with more than 1 byte if bytelist[i+1] & 0x80 > 0: Len_num = bytelist[i+1] - 0x80 @@ -196,14 +196,14 @@ def BERTLV_parser(bytelist): ''' BERTLV_parser([0xAA, ..., 0xFF]) -> [([T], L, [V]), ([T], L, [V]), ...] - + loops on the input bytes with the "first_BERTLV_parser()" function returns a list of 3-Tuples containing BERTLV records ''' ret = [] while len(bytelist) > 0: T, L, V = first_BERTLV_parser(bytelist) - #if T == 0xFF: + #if T == 0xFF: # break # padding bytes ret.append( (T[1:], L[1], V) ) # need to manage lengths of Tag and Length @@ -213,14 +213,14 @@ def decode_BCD(data=[]): ''' decode_BCD([0x21, 0xFE, 0xA3]) -> '121415310' - + to decode serial number (IMSI, ICCID...) from list of bytes ''' string = '' for B in data: string += str( B & 0x0F ) string += str( B >> 4 ) - return string + return string ####################################################### @@ -230,10 +230,10 @@ ''' input / output wrapping class for APDU communications - + allows to keep track of communications and exchanged commands - + based on the python "deque" fifo-like object ''' @@ -242,13 +242,13 @@ initializes apdu_stack with the maximum of IO to keep track of ''' self.apdu_stack = deque([], limit) - + def push(self, apdu_response): ''' stacks the returned response into the apdu_stack ''' self.apdu_stack.append( apdu_response ) - + def __repr__(self): ''' represents the whole stack of responses pushed on @@ -257,10 +257,9 @@ for apdu in self.apdu_stack: s += apdu.__repr__() + '\n' return s - + def __call__(self): ''' calling the apdu_stack returns the last response pushed on it ''' return self.apdu_stack[-1] - -- To view, visit https://gerrit.osmocom.org/c/osmo-sim-auth/+/20909 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmo-sim-auth Gerrit-Branch: master Gerrit-Change-Id: Ie8a5fc47775fe7d7fe0e19f7378ffda104fa6112 Gerrit-Change-Number: 20909 Gerrit-PatchSet: 1 Gerrit-Owner: Ludovic Rousseau <ludovic.rousseau+osmocom at free.fr> Gerrit-MessageType: newchange -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20201025/d9ffd25a/attachment.htm>