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>