cosmetic: Switch to consistent four-spaces indent; run autopep8

We had a mixture of tab and 4space based indenting, which is a bad
idea.  4space is the standard in python, so convert all our code to
that.  The result unfortuantely still shoed even more inconsistencies,
so I've decided to run autopep8 on the entire code base.

Change-Id: I4a4b1b444a2f43fab05fc5d2c8a7dd6ddecb5f07
This commit is contained in:
Harald Welte
2022-02-10 18:05:45 +01:00
parent 181c7c5930
commit c91085e744
29 changed files with 7501 additions and 6549 deletions

View File

@@ -41,6 +41,7 @@ from pySim.ts_51_011 import EF, EF_AD
from pySim.card_handler import * from pySim.card_handler import *
from pySim.utils import * from pySim.utils import *
def parse_options(): def parse_options():
parser = OptionParser(usage="usage: %prog [options]") parser = OptionParser(usage="usage: %prog [options]")
@@ -190,7 +191,6 @@ def parse_options():
parser.add_option("--read-csv", dest="read_csv", metavar="FILE", parser.add_option("--read-csv", dest="read_csv", metavar="FILE",
help="Read parameters from CSV file rather than command line") help="Read parameters from CSV file rather than command line")
parser.add_option("--write-csv", dest="write_csv", metavar="FILE", parser.add_option("--write-csv", dest="write_csv", metavar="FILE",
help="Append generated parameters in CSV file", help="Append generated parameters in CSV file",
) )
@@ -215,12 +215,14 @@ def parse_options():
if options.source == 'csv': if options.source == 'csv':
if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False): if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False):
parser.error("CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode") parser.error(
"CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode")
if options.read_csv is None: if options.read_csv is None:
parser.error("CSV mode requires a CSV input file") parser.error("CSV mode requires a CSV input file")
elif options.source == 'cmdline': elif options.source == 'cmdline':
if ((options.imsi is None) or (options.iccid is None)) and (options.num is None): if ((options.imsi is None) or (options.iccid is None)) and (options.num is None):
parser.error("If either IMSI or ICCID isn't specified, num is required") parser.error(
"If either IMSI or ICCID isn't specified, num is required")
else: else:
parser.error("Only `cmdline' and `csv' sources supported") parser.error("Only `cmdline' and `csv' sources supported")
@@ -232,7 +234,8 @@ def parse_options():
if (options.batch_mode): if (options.batch_mode):
if (options.imsi is not None) or (options.iccid is not None): if (options.imsi is not None) or (options.iccid is not None):
parser.error("Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more informations") parser.error(
"Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more information")
if args: if args:
parser.error("Extraneous arguments") parser.error("Extraneous arguments")
@@ -246,15 +249,19 @@ def _digits(secret, usage, len, num):
d = ''.join(['%02d' % x for x in s.digest()]) d = ''.join(['%02d' % x for x in s.digest()])
return d[0:len] return d[0:len]
def _mcc_mnc_digits(mcc, mnc): def _mcc_mnc_digits(mcc, mnc):
return '%s%s' % (mcc, mnc) return '%s%s' % (mcc, mnc)
def _cc_digits(cc): def _cc_digits(cc):
return ('%03d' if cc > 100 else '%02d') % cc return ('%03d' if cc > 100 else '%02d') % cc
def _isnum(s, l=-1): def _isnum(s, l=-1):
return s.isdigit() and ((l == -1) or (len(s) == l)) return s.isdigit() and ((l == -1) or (len(s) == l))
def _ishex(s, l=-1): def _ishex(s, l=-1):
hc = '0123456789abcdef' hc = '0123456789abcdef'
return all([x in hc for x in s.lower()]) and ((l == -1) or (len(s) == l)) return all([x in hc for x in s.lower()]) and ((l == -1) or (len(s) == l))
@@ -272,7 +279,8 @@ def _dbi_binary_quote(s):
for i in range(1, 256): for i in range(1, 256):
if i == 39: if i == 39:
continue continue
sum_ = cnt.get(i, 0) + cnt.get((i+1)&0xff, 0) + cnt.get((i+39)&0xff, 0) sum_ = cnt.get(i, 0) + cnt.get((i+1) & 0xff, 0) + \
cnt.get((i+39) & 0xff, 0)
if sum_ < m: if sum_ < m:
m = sum_ m = sum_
e = i e = i
@@ -292,6 +300,7 @@ def _dbi_binary_quote(s):
return ''.join(out) return ''.join(out)
def gen_parameters(opts): def gen_parameters(opts):
"""Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the """Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the
options given by the user""" options given by the user"""
@@ -331,7 +340,8 @@ def gen_parameters(opts):
'Start with \'+\' for international numbers.') 'Start with \'+\' for international numbers.')
if len(msisdn) > 10 * 2: if len(msisdn) > 10 * 2:
# TODO: Support MSISDN of length > 20 (10 Bytes) # TODO: Support MSISDN of length > 20 (10 Bytes)
raise ValueError('MSISDNs longer than 20 digits are not (yet) supported.') raise ValueError(
'MSISDNs longer than 20 digits are not (yet) supported.')
# ICCID (19 digits, E.118), though some phase1 vendors use 20 :( # ICCID (19 digits, E.118), though some phase1 vendors use 20 :(
if opts.iccid is not None: if opts.iccid is not None:
@@ -406,7 +416,8 @@ def gen_parameters(opts):
else: else:
smsc = '00%d' % opts.country + '5555' # Hack ... smsc = '00%d' % opts.country + '5555' # Hack ...
smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + swap_nibbles(rpad(smsc, 20)) smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + \
swap_nibbles(rpad(smsc, 20))
smsp = ( smsp = (
'e1' + # Parameters indicator 'e1' + # Parameters indicator
@@ -456,7 +467,8 @@ def gen_parameters(opts):
epdg_mcc = opts.epdgSelection[:3] epdg_mcc = opts.epdgSelection[:3]
epdg_mnc = opts.epdgSelection[3:] epdg_mnc = opts.epdgSelection[3:]
if not epdg_mcc.isdigit() or not epdg_mnc.isdigit(): if not epdg_mcc.isdigit() or not epdg_mnc.isdigit():
raise ValueError('PLMN for ePDG Selection must only contain decimal digits') raise ValueError(
'PLMN for ePDG Selection must only contain decimal digits')
# Return that # Return that
return { return {
@@ -511,6 +523,7 @@ def write_params_csv(opts, params):
cw.writerow([params[x] for x in row]) cw.writerow([params[x] for x in row])
f.close() f.close()
def _read_params_csv(opts, iccid=None, imsi=None): def _read_params_csv(opts, iccid=None, imsi=None):
import csv import csv
f = open(opts.read_csv, 'r') f = open(opts.read_csv, 'r')
@@ -539,6 +552,7 @@ def _read_params_csv(opts, iccid=None, imsi=None):
f.close() f.close()
return None return None
def read_params_csv(opts, imsi=None, iccid=None): def read_params_csv(opts, imsi=None, iccid=None):
row = _read_params_csv(opts, iccid=iccid, imsi=imsi) row = _read_params_csv(opts, iccid=iccid, imsi=imsi)
if row is not None: if row is not None:
@@ -565,9 +579,11 @@ def read_params_csv(opts, imsi=None, iccid=None):
try: try:
try_encode = h2b(pin_adm) try_encode = h2b(pin_adm)
except ValueError: except ValueError:
raise ValueError("pin_adm_hex needs to be hex encoded using this option") raise ValueError(
"pin_adm_hex needs to be hex encoded using this option")
else: else:
raise ValueError("pin_adm_hex needs to be exactly 16 digits (hex encoded)") raise ValueError(
"pin_adm_hex needs to be exactly 16 digits (hex encoded)")
return row return row
@@ -597,12 +613,14 @@ def write_params_hlr(opts, params):
'(subscriber_id, algorithm_id, a3a8_ki)' + '(subscriber_id, algorithm_id, a3a8_ki)' +
'VALUES ' + 'VALUES ' +
'(?,?,?)', '(?,?,?)',
[ sub_id, 2, sqlite3.Binary(_dbi_binary_quote(h2b(params['ki']))) ], [sub_id, 2, sqlite3.Binary(
_dbi_binary_quote(h2b(params['ki'])))],
) )
conn.commit() conn.commit()
conn.close() conn.close()
def write_parameters(opts, params): def write_parameters(opts, params):
write_params_csv(opts, params) write_params_csv(opts, params)
write_params_hlr(opts, params) write_params_hlr(opts, params)
@@ -611,6 +629,7 @@ def write_parameters(opts, params):
BATCH_STATE = ['name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num'] BATCH_STATE = ['name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num']
BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki'] BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki']
def init_batch(opts): def init_batch(opts):
# Need to do something ? # Need to do something ?
if not opts.batch_mode: if not opts.batch_mode:

View File

@@ -45,6 +45,7 @@ option_parser = argparse.ArgumentParser(prog='pySim-read',
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
argparse_add_reader_args(option_parser) argparse_add_reader_args(option_parser)
def select_app(adf: str, card: SimCard): def select_app(adf: str, card: SimCard):
"""Select application by its AID""" """Select application by its AID"""
sw = 0 sw = 0
@@ -63,6 +64,7 @@ def select_app(adf:str, card:SimCard):
return sw return sw
if __name__ == '__main__': if __name__ == '__main__':
# Parse options # Parse options
@@ -270,7 +272,8 @@ if __name__ == '__main__':
if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']): if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']):
(res, sw) = usim_card.read_epdgid() (res, sw) = usim_card.read_epdgid()
if sw == '9000': if sw == '9000':
print("ePDGId:\n%s" % (len(res) and res or '\tNot available\n',)) print("ePDGId:\n%s" %
(len(res) and res or '\tNot available\n',))
else: else:
print("ePDGId: Can't read, response code = %s" % (sw,)) print("ePDGId: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
@@ -297,7 +300,8 @@ if __name__ == '__main__':
try: try:
if isim_card.file_exists(EF_ISIM_ADF_map['PCSCF']): if isim_card.file_exists(EF_ISIM_ADF_map['PCSCF']):
res = isim_card.read_pcscf() res = isim_card.read_pcscf()
print("P-CSCF:\n%s" % (len(res) and res or '\tNot available\n',)) print("P-CSCF:\n%s" %
(len(res) and res or '\tNot available\n',))
except Exception as e: except Exception as e:
print("P-CSCF: Can't read file -- " + str(e)) print("P-CSCF: Can't read file -- " + str(e))
@@ -306,9 +310,11 @@ if __name__ == '__main__':
if isim_card.file_exists(EF_ISIM_ADF_map['DOMAIN']): if isim_card.file_exists(EF_ISIM_ADF_map['DOMAIN']):
(res, sw) = isim_card.read_domain() (res, sw) = isim_card.read_domain()
if sw == '9000': if sw == '9000':
print("Home Network Domain Name: %s" % (len(res) and res or 'Not available',)) print("Home Network Domain Name: %s" %
(len(res) and res or 'Not available',))
else: else:
print("Home Network Domain Name: Can't read, response code = %s" % (sw,)) print(
"Home Network Domain Name: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("Home Network Domain Name: Can't read file -- " + str(e)) print("Home Network Domain Name: Can't read file -- " + str(e))
@@ -317,9 +323,11 @@ if __name__ == '__main__':
if isim_card.file_exists(EF_ISIM_ADF_map['IMPI']): if isim_card.file_exists(EF_ISIM_ADF_map['IMPI']):
(res, sw) = isim_card.read_impi() (res, sw) = isim_card.read_impi()
if sw == '9000': if sw == '9000':
print("IMS private user identity: %s" % (len(res) and res or 'Not available',)) print("IMS private user identity: %s" %
(len(res) and res or 'Not available',))
else: else:
print("IMS private user identity: Can't read, response code = %s" % (sw,)) print(
"IMS private user identity: Can't read, response code = %s" % (sw,))
except Exception as e: except Exception as e:
print("IMS private user identity: Can't read file -- " + str(e)) print("IMS private user identity: Can't read file -- " + str(e))
@@ -327,7 +335,8 @@ if __name__ == '__main__':
try: try:
if isim_card.file_exists(EF_ISIM_ADF_map['IMPU']): if isim_card.file_exists(EF_ISIM_ADF_map['IMPU']):
res = isim_card.read_impu() res = isim_card.read_impu()
print("IMS public user identity:\n%s" % (len(res) and res or '\tNot available\n',)) print("IMS public user identity:\n%s" %
(len(res) and res or '\tNot available\n',))
except Exception as e: except Exception as e:
print("IMS public user identity: Can't read file -- " + str(e)) print("IMS public user identity: Can't read file -- " + str(e))
@@ -335,7 +344,8 @@ if __name__ == '__main__':
try: try:
if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']): if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']):
res = isim_card.read_iari() res = isim_card.read_iari()
print("UICC IARI:\n%s" % (len(res) and res or '\tNot available\n',)) print("UICC IARI:\n%s" %
(len(res) and res or '\tNot available\n',))
except Exception as e: except Exception as e:
print("UICC IARI: Can't read file -- " + str(e)) print("UICC IARI: Can't read file -- " + str(e))

View File

@@ -61,6 +61,7 @@ import pySim.sysmocom_sja2
from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field
def init_card(sl): def init_card(sl):
""" """
Detect card in reader and setup card profile and runtime state. This Detect card in reader and setup card profile and runtime state. This
@@ -117,8 +118,10 @@ def init_card(sl):
return rs, card return rs, card
class PysimApp(cmd2.Cmd): class PysimApp(cmd2.Cmd):
CUSTOM_CATEGORY = 'pySim Commands' CUSTOM_CATEGORY = 'pySim Commands'
def __init__(self, card, rs, sl, ch, script=None): def __init__(self, card, rs, sl, ch, script=None):
super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False, super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
use_ipython=True, auto_load_commands=False, startup_script=script) use_ipython=True, auto_load_commands=False, startup_script=script)
@@ -137,7 +140,8 @@ class PysimApp(cmd2.Cmd):
self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write', self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write',
onchange_cb=self._onchange_conserve_write)) onchange_cb=self._onchange_conserve_write))
self.json_pretty_print = True self.json_pretty_print = True
self.add_settable(cmd2.Settable('json_pretty_print', bool, 'Pretty-Print JSON output')) self.add_settable(cmd2.Settable('json_pretty_print',
bool, 'Pretty-Print JSON output'))
self.apdu_trace = False self.apdu_trace = False
self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card', self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',
onchange_cb=self._onchange_apdu_trace)) onchange_cb=self._onchange_apdu_trace))
@@ -166,7 +170,8 @@ class PysimApp(cmd2.Cmd):
# When a card object and a runtime state is present, (re)equip pySim-shell with everything that is # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is
# needed to operate on cards. # needed to operate on cards.
if self.card and self.rs: if self.card and self.rs:
self._onchange_conserve_write('conserve_write', False, self.conserve_write) self._onchange_conserve_write(
'conserve_write', False, self.conserve_write)
self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace) self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace)
self.register_command_set(Iso7816Commands()) self.register_command_set(Iso7816Commands())
self.register_command_set(PySimCommands()) self.register_command_set(PySimCommands())
@@ -211,7 +216,8 @@ class PysimApp(cmd2.Cmd):
def update_prompt(self): def update_prompt(self):
if self.rs: if self.rs:
path_list = self.rs.selected_file.fully_qualified_path(not self.numeric_path) path_list = self.rs.selected_file.fully_qualified_path(
not self.numeric_path)
self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list)) self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list))
else: else:
self.prompt = 'pySIM-shell (no card)> ' self.prompt = 'pySIM-shell (no card)> '
@@ -234,10 +240,12 @@ class PysimApp(cmd2.Cmd):
class InterceptStderr(list): class InterceptStderr(list):
def __init__(self): def __init__(self):
self._stderr_backup = sys.stderr self._stderr_backup = sys.stderr
def __enter__(self): def __enter__(self):
self._stringio_stderr = StringIO() self._stringio_stderr = StringIO()
sys.stderr = self._stringio_stderr sys.stderr = self._stringio_stderr
return self return self
def __exit__(self, *args): def __exit__(self, *args):
self.stderr = self._stringio_stderr.getvalue().strip() self.stderr = self._stringio_stderr.getvalue().strip()
del self._stringio_stderr del self._stringio_stderr
@@ -305,7 +313,8 @@ class PysimApp(cmd2.Cmd):
return -1 return -1
bulk_script_parser = argparse.ArgumentParser() bulk_script_parser = argparse.ArgumentParser()
bulk_script_parser.add_argument('script_path', help="path to the script file") bulk_script_parser.add_argument(
'script_path', help="path to the script file")
bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs', bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs',
action='store_true') action='store_true')
bulk_script_parser.add_argument('--tries', type=int, default=2, bulk_script_parser.add_argument('--tries', type=int, default=2,
@@ -350,13 +359,14 @@ class PysimApp(cmd2.Cmd):
if rc == 0: if rc == 0:
success_count = success_count + 1 success_count = success_count + 1
self._show_success_sign() self._show_success_sign()
self.poutput("Statistics: success :%i, failure: %i" % (success_count, fail_count)) self.poutput("Statistics: success :%i, failure: %i" % (
success_count, fail_count))
break break
else: else:
fail_count = fail_count + 1 fail_count = fail_count + 1
self._show_failure_sign() self._show_failure_sign()
self.poutput("Statistics: success :%i, failure: %i" % (success_count, fail_count)) self.poutput("Statistics: success :%i, failure: %i" % (
success_count, fail_count))
# Depending on success or failure, the card goes either in the "error" bin or in the # Depending on success or failure, the card goes either in the "error" bin or in the
# "done" bin. # "done" bin.
@@ -391,7 +401,8 @@ class PysimApp(cmd2.Cmd):
self.poutput("") self.poutput("")
fail_count = fail_count + 1 fail_count = fail_count + 1
self._show_failure_sign() self._show_failure_sign()
self.poutput("Statistics: success :%i, failure: %i" % (success_count, fail_count)) self.poutput("Statistics: success :%i, failure: %i" %
(success_count, fail_count))
first = False first = False
@@ -404,16 +415,21 @@ class PysimApp(cmd2.Cmd):
"""Echo (print) a string on the console""" """Echo (print) a string on the console"""
self.poutput(opts.string) self.poutput(opts.string)
@with_default_category('pySim Commands') @with_default_category('pySim Commands')
class PySimCommands(CommandSet): class PySimCommands(CommandSet):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
dir_parser = argparse.ArgumentParser() dir_parser = argparse.ArgumentParser()
dir_parser.add_argument('--fids', help='Show file identifiers', action='store_true') dir_parser.add_argument(
dir_parser.add_argument('--names', help='Show file names', action='store_true') '--fids', help='Show file identifiers', action='store_true')
dir_parser.add_argument('--apps', help='Show applications', action='store_true') dir_parser.add_argument(
dir_parser.add_argument('--all', help='Show all selectable identifiers and names', action='store_true') '--names', help='Show file names', action='store_true')
dir_parser.add_argument(
'--apps', help='Show applications', action='store_true')
dir_parser.add_argument(
'--all', help='Show all selectable identifiers and names', action='store_true')
@cmd2.with_argparser(dir_parser) @cmd2.with_argparser(dir_parser)
def do_dir(self, opts): def do_dir(self, opts):
@@ -430,8 +446,10 @@ class PySimCommands(CommandSet):
flags += ['ANAMES', 'AIDS'] flags += ['ANAMES', 'AIDS']
else: else:
flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES'] flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
selectables = list(self._cmd.rs.selected_file.get_selectable_names(flags = flags)) selectables = list(
directory_str = tabulate_str_list(selectables, width = 79, hspace = 2, lspace = 1, align_left = True) self._cmd.rs.selected_file.get_selectable_names(flags=flags))
directory_str = tabulate_str_list(
selectables, width=79, hspace=2, lspace=1, align_left=True)
path_list = self._cmd.rs.selected_file.fully_qualified_path(True) path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
self._cmd.poutput('/'.join(path_list)) self._cmd.poutput('/'.join(path_list))
path_list = self._cmd.rs.selected_file.fully_qualified_path(False) path_list = self._cmd.rs.selected_file.fully_qualified_path(False)
@@ -441,7 +459,8 @@ class PySimCommands(CommandSet):
def walk(self, indent=0, action=None, context=None): def walk(self, indent=0, action=None, context=None):
"""Recursively walk through the file system, starting at the currently selected DF""" """Recursively walk through the file system, starting at the currently selected DF"""
files = self._cmd.rs.selected_file.get_selectables(flags = ['FNAMES', 'ANAMES']) files = self._cmd.rs.selected_file.get_selectables(
flags=['FNAMES', 'ANAMES'])
for f in files: for f in files:
if not action: if not action:
output_str = " " * indent + str(f) + (" " * 250) output_str = " " * indent + str(f) + (" " * 250)
@@ -461,7 +480,8 @@ class PySimCommands(CommandSet):
skip_df = True skip_df = True
df = self._cmd.rs.selected_file df = self._cmd.rs.selected_file
df_path_list = df.fully_qualified_path(True) df_path_list = df.fully_qualified_path(True)
df_skip_reason_str = '/'.join(df_path_list) + "/" + str(f) + ", " + str(e) df_skip_reason_str = '/'.join(df_path_list) + \
"/" + str(f) + ", " + str(e)
if context: if context:
context['DF_SKIP'] += 1 context['DF_SKIP'] += 1
context['DF_SKIP_REASON'].append(df_skip_reason_str) context['DF_SKIP_REASON'].append(df_skip_reason_str)
@@ -492,7 +512,8 @@ class PySimCommands(CommandSet):
df = self._cmd.rs.selected_file df = self._cmd.rs.selected_file
if not isinstance(df, CardDF): if not isinstance(df, CardDF):
raise RuntimeError("currently selected file %s is not a DF or ADF" % str(df)) raise RuntimeError(
"currently selected file %s is not a DF or ADF" % str(df))
df_path_list = df.fully_qualified_path(True) df_path_list = df.fully_qualified_path(True)
df_path_list_fid = df.fully_qualified_path(False) df_path_list_fid = df.fully_qualified_path(False)
@@ -500,10 +521,12 @@ class PySimCommands(CommandSet):
file_str = '/'.join(df_path_list) + "/" + str(filename) file_str = '/'.join(df_path_list) + "/" + str(filename)
self._cmd.poutput(boxed_heading_str(file_str)) self._cmd.poutput(boxed_heading_str(file_str))
self._cmd.poutput("# directory: %s (%s)" % ('/'.join(df_path_list), '/'.join(df_path_list_fid))) self._cmd.poutput("# directory: %s (%s)" %
('/'.join(df_path_list), '/'.join(df_path_list_fid)))
try: try:
fcp_dec = self._cmd.rs.select(filename, self._cmd) fcp_dec = self._cmd.rs.select(filename, self._cmd)
self._cmd.poutput("# file: %s (%s)" % (self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid)) self._cmd.poutput("# file: %s (%s)" % (
self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid))
fd = fcp_dec['file_descriptor'] fd = fcp_dec['file_descriptor']
structure = fd['structure'] structure = fd['structure']
@@ -522,7 +545,8 @@ class PySimCommands(CommandSet):
num_of_rec = fd['num_of_rec'] num_of_rec = fd['num_of_rec']
for r in range(1, num_of_rec + 1): for r in range(1, num_of_rec + 1):
result = self._cmd.rs.read_record(r) result = self._cmd.rs.read_record(r)
self._cmd.poutput("update_record %d %s" % (r, str(result[0]))) self._cmd.poutput("update_record %d %s" %
(r, str(result[0])))
# When the select response does not return the number of records, read until we hit the # When the select response does not return the number of records, read until we hit the
# first record that cannot be read. # first record that cannot be read.
else: else:
@@ -537,7 +561,8 @@ class PySimCommands(CommandSet):
# Some other problem occurred # Some other problem occurred
else: else:
raise e raise e
self._cmd.poutput("update_record %d %s" % (r, str(result[0]))) self._cmd.poutput("update_record %d %s" %
(r, str(result[0])))
r = r + 1 r = r + 1
elif structure == 'ber_tlv': elif structure == 'ber_tlv':
tags = self._cmd.rs.retrieve_tags() tags = self._cmd.rs.retrieve_tags()
@@ -546,9 +571,11 @@ class PySimCommands(CommandSet):
(tag, l, val, remainer) = bertlv_parse_one(h2b(result[0])) (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val))) self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
else: else:
raise RuntimeError('Unsupported structure "%s" of file "%s"' % (structure, filename)) raise RuntimeError(
'Unsupported structure "%s" of file "%s"' % (structure, filename))
except Exception as e: except Exception as e:
bad_file_str = '/'.join(df_path_list) + "/" + str(filename) + ", " + str(e) bad_file_str = '/'.join(df_path_list) + \
"/" + str(filename) + ", " + str(e)
self._cmd.poutput("# bad file: %s" % bad_file_str) self._cmd.poutput("# bad file: %s" % bad_file_str)
context['ERR'] += 1 context['ERR'] += 1
context['BAD'].append(bad_file_str) context['BAD'].append(bad_file_str)
@@ -562,12 +589,14 @@ class PySimCommands(CommandSet):
self._cmd.poutput("#") self._cmd.poutput("#")
export_parser = argparse.ArgumentParser() export_parser = argparse.ArgumentParser()
export_parser.add_argument('--filename', type=str, default=None, help='only export specific file') export_parser.add_argument(
'--filename', type=str, default=None, help='only export specific file')
@cmd2.with_argparser(export_parser) @cmd2.with_argparser(export_parser)
def do_export(self, opts): def do_export(self, opts):
"""Export files to script that can be imported back later""" """Export files to script that can be imported back later"""
context = {'ERR':0, 'COUNT':0, 'BAD':[], 'DF_SKIP':0, 'DF_SKIP_REASON':[]} context = {'ERR': 0, 'COUNT': 0, 'BAD': [],
'DF_SKIP': 0, 'DF_SKIP_REASON': []}
if opts.filename: if opts.filename:
self.export(opts.filename, context) self.export(opts.filename, context)
else: else:
@@ -580,16 +609,20 @@ class PySimCommands(CommandSet):
for b in context['BAD']: for b in context['BAD']:
self._cmd.poutput("# " + b) self._cmd.poutput("# " + b)
self._cmd.poutput("# skipped dedicated files(s): %u" % context['DF_SKIP']) self._cmd.poutput("# skipped dedicated files(s): %u" %
context['DF_SKIP'])
for b in context['DF_SKIP_REASON']: for b in context['DF_SKIP_REASON']:
self._cmd.poutput("# " + b) self._cmd.poutput("# " + b)
if context['ERR'] and context['DF_SKIP']: if context['ERR'] and context['DF_SKIP']:
raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)" % (context['ERR'], context['DF_SKIP'])) raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)" % (
context['ERR'], context['DF_SKIP']))
elif context['ERR']: elif context['ERR']:
raise RuntimeError("unable to export %i elementary file(s)" % context['ERR']) raise RuntimeError(
"unable to export %i elementary file(s)" % context['ERR'])
elif context['DF_SKIP']: elif context['DF_SKIP']:
raise RuntimeError("unable to export %i dedicated files(s)" % context['ERR']) raise RuntimeError(
"unable to export %i dedicated files(s)" % context['ERR'])
def do_reset(self, opts): def do_reset(self, opts):
"""Reset the Card.""" """Reset the Card."""
@@ -612,18 +645,22 @@ class PySimCommands(CommandSet):
pin_adm = sanitize_pin_adm(arg) pin_adm = sanitize_pin_adm(arg)
else: else:
# try to find an ADM-PIN if none is specified # try to find an ADM-PIN if none is specified
result = card_key_provider_get_field('ADM1', key='ICCID', value=self._cmd.iccid) result = card_key_provider_get_field(
'ADM1', key='ICCID', value=self._cmd.iccid)
pin_adm = sanitize_pin_adm(result) pin_adm = sanitize_pin_adm(result)
if pin_adm: if pin_adm:
self._cmd.poutput("found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid)) self._cmd.poutput(
"found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid))
else: else:
raise ValueError("cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid)) raise ValueError(
"cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid))
if pin_adm: if pin_adm:
self._cmd.card.verify_adm(h2b(pin_adm)) self._cmd.card.verify_adm(h2b(pin_adm))
else: else:
raise ValueError("error: cannot authenticate, no adm-pin!") raise ValueError("error: cannot authenticate, no adm-pin!")
@with_default_category('ISO7816 Commands') @with_default_category('ISO7816 Commands')
class Iso7816Commands(CommandSet): class Iso7816Commands(CommandSet):
def __init__(self): def __init__(self):
@@ -633,8 +670,10 @@ class Iso7816Commands(CommandSet):
"""SELECT a File (ADF/DF/EF)""" """SELECT a File (ADF/DF/EF)"""
if len(opts.arg_list) == 0: if len(opts.arg_list) == 0:
path_list = self._cmd.rs.selected_file.fully_qualified_path(True) path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(False) path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(
self._cmd.poutput("currently selected file: " + '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")") False)
self._cmd.poutput("currently selected file: " +
'/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
return return
path = opts.arg_list[0] path = opts.arg_list[0]
@@ -654,17 +693,22 @@ class Iso7816Commands(CommandSet):
if str(code).upper() not in auto: if str(code).upper() not in auto:
return sanitize_pin_adm(code) return sanitize_pin_adm(code)
result = card_key_provider_get_field(str(code), key='ICCID', value=self._cmd.iccid) result = card_key_provider_get_field(
str(code), key='ICCID', value=self._cmd.iccid)
result = sanitize_pin_adm(result) result = sanitize_pin_adm(result)
if result: if result:
self._cmd.poutput("found %s '%s' for ICCID '%s'" % (code.upper(), result, self._cmd.iccid)) self._cmd.poutput("found %s '%s' for ICCID '%s'" %
(code.upper(), result, self._cmd.iccid))
else: else:
self._cmd.poutput("cannot find %s for ICCID '%s'" % (code.upper(), self._cmd.iccid)) self._cmd.poutput("cannot find %s for ICCID '%s'" %
(code.upper(), self._cmd.iccid))
return result return result
verify_chv_parser = argparse.ArgumentParser() verify_chv_parser = argparse.ArgumentParser()
verify_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)') verify_chv_parser.add_argument(
verify_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source') '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
verify_chv_parser.add_argument(
'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
@cmd2.with_argparser(verify_chv_parser) @cmd2.with_argparser(verify_chv_parser)
def do_verify_chv(self, opts): def do_verify_chv(self, opts):
@@ -674,34 +718,44 @@ class Iso7816Commands(CommandSet):
self._cmd.poutput("CHV verification successful") self._cmd.poutput("CHV verification successful")
unblock_chv_parser = argparse.ArgumentParser() unblock_chv_parser = argparse.ArgumentParser()
unblock_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)') unblock_chv_parser.add_argument(
unblock_chv_parser.add_argument('puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source') '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
unblock_chv_parser.add_argument('new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source') unblock_chv_parser.add_argument(
'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source')
unblock_chv_parser.add_argument(
'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
@cmd2.with_argparser(unblock_chv_parser) @cmd2.with_argparser(unblock_chv_parser)
def do_unblock_chv(self, opts): def do_unblock_chv(self, opts):
"""Unblock PIN code using specified PUK code""" """Unblock PIN code using specified PUK code"""
new_pin = self.get_code(opts.new_pin_code) new_pin = self.get_code(opts.new_pin_code)
puk = self.get_code(opts.puk_code) puk = self.get_code(opts.puk_code)
(data, sw) = self._cmd.card._scc.unblock_chv(opts.pin_nr, h2b(puk), h2b(new_pin)) (data, sw) = self._cmd.card._scc.unblock_chv(
opts.pin_nr, h2b(puk), h2b(new_pin))
self._cmd.poutput("CHV unblock successful") self._cmd.poutput("CHV unblock successful")
change_chv_parser = argparse.ArgumentParser() change_chv_parser = argparse.ArgumentParser()
change_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)') change_chv_parser.add_argument(
change_chv_parser.add_argument('pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source') '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
change_chv_parser.add_argument('new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source') change_chv_parser.add_argument(
'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
change_chv_parser.add_argument(
'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source')
@cmd2.with_argparser(change_chv_parser) @cmd2.with_argparser(change_chv_parser)
def do_change_chv(self, opts): def do_change_chv(self, opts):
"""Change PIN code to a new PIN code""" """Change PIN code to a new PIN code"""
new_pin = self.get_code(opts.new_pin_code) new_pin = self.get_code(opts.new_pin_code)
pin = self.get_code(opts.pin_code) pin = self.get_code(opts.pin_code)
(data, sw) = self._cmd.card._scc.change_chv(opts.pin_nr, h2b(pin), h2b(new_pin)) (data, sw) = self._cmd.card._scc.change_chv(
opts.pin_nr, h2b(pin), h2b(new_pin))
self._cmd.poutput("CHV change successful") self._cmd.poutput("CHV change successful")
disable_chv_parser = argparse.ArgumentParser() disable_chv_parser = argparse.ArgumentParser()
disable_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)') disable_chv_parser.add_argument(
disable_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source') '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
disable_chv_parser.add_argument(
'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
@cmd2.with_argparser(disable_chv_parser) @cmd2.with_argparser(disable_chv_parser)
def do_disable_chv(self, opts): def do_disable_chv(self, opts):
@@ -711,8 +765,10 @@ class Iso7816Commands(CommandSet):
self._cmd.poutput("CHV disable successful") self._cmd.poutput("CHV disable successful")
enable_chv_parser = argparse.ArgumentParser() enable_chv_parser = argparse.ArgumentParser()
enable_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)') enable_chv_parser.add_argument(
enable_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source') '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)')
enable_chv_parser.add_argument(
'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
@cmd2.with_argparser(enable_chv_parser) @cmd2.with_argparser(enable_chv_parser)
def do_enable_chv(self, opts): def do_enable_chv(self, opts):
@@ -736,20 +792,24 @@ class Iso7816Commands(CommandSet):
return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict) return self._cmd.index_based_complete(text, line, begidx, endidx, index_dict=index_dict)
open_chan_parser = argparse.ArgumentParser() open_chan_parser = argparse.ArgumentParser()
open_chan_parser.add_argument('chan_nr', type=int, default=0, help='Channel Number') open_chan_parser.add_argument(
'chan_nr', type=int, default=0, help='Channel Number')
@cmd2.with_argparser(open_chan_parser) @cmd2.with_argparser(open_chan_parser)
def do_open_channel(self, opts): def do_open_channel(self, opts):
"""Open a logical channel.""" """Open a logical channel."""
(data, sw) = self._cmd.card._scc.manage_channel(mode='open', lchan_nr=opts.chan_nr) (data, sw) = self._cmd.card._scc.manage_channel(
mode='open', lchan_nr=opts.chan_nr)
close_chan_parser = argparse.ArgumentParser() close_chan_parser = argparse.ArgumentParser()
close_chan_parser.add_argument('chan_nr', type=int, default=0, help='Channel Number') close_chan_parser.add_argument(
'chan_nr', type=int, default=0, help='Channel Number')
@cmd2.with_argparser(close_chan_parser) @cmd2.with_argparser(close_chan_parser)
def do_close_channel(self, opts): def do_close_channel(self, opts):
"""Close a logical channel.""" """Close a logical channel."""
(data, sw) = self._cmd.card._scc.manage_channel(mode='close', lchan_nr=opts.chan_nr) (data, sw) = self._cmd.card._scc.manage_channel(
mode='close', lchan_nr=opts.chan_nr)
def do_status(self, opts): def do_status(self, opts):
"""Perform the STATUS command.""" """Perform the STATUS command."""
@@ -768,7 +828,8 @@ class Iso7816Commands(CommandSet):
"""Perform the SUSPEND UICC command. Only supported on some UICC.""" """Perform the SUSPEND UICC command. Only supported on some UICC."""
(duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs, (duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs,
max_len_secs=opts.max_duration_secs) max_len_secs=opts.max_duration_secs)
self._cmd.poutput('Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw)) self._cmd.poutput(
'Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw))
option_parser = argparse.ArgumentParser(prog='pySim-shell', description='interactive SIM card shell', option_parser = argparse.ArgumentParser(prog='pySim-shell', description='interactive SIM card shell',
@@ -778,7 +839,8 @@ argparse_add_reader_args(option_parser)
global_group = option_parser.add_argument_group('General Options') global_group = option_parser.add_argument_group('General Options')
global_group.add_argument('--script', metavar='PATH', default=None, global_group.add_argument('--script', metavar='PATH', default=None,
help='script with pySim-shell commands to be executed automatically at start-up') help='script with pySim-shell commands to be executed automatically at start-up')
global_group.add_argument('--csv', metavar='FILE', default=None, help='Read card data from CSV file') global_group.add_argument('--csv', metavar='FILE',
default=None, help='Read card data from CSV file')
global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE", global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE",
help="Use automatic card handling machine") help="Use automatic card handling machine")
@@ -834,7 +896,8 @@ if __name__ == '__main__':
traceback.print_exc() traceback.print_exc()
print("---------------------8<---------------------") print("---------------------8<---------------------")
print("(you may still try to recover from this manually by using the 'equip' command.)") print("(you may still try to recover from this manually by using the 'equip' command.)")
print(" it should also be noted that some readers may behave strangely when no card") print(
" it should also be noted that some readers may behave strangely when no card")
print(" is inserted.)") print(" is inserted.)")
print("") print("")
app = PysimApp(None, None, sl, ch, opts.script) app = PysimApp(None, None, sl, ch, opts.script)

View File

@@ -1 +0,0 @@

View File

@@ -34,26 +34,32 @@ from pySim.tlv import *
# various BER-TLV encoded Data Objects (DOs) # various BER-TLV encoded Data Objects (DOs)
class AidRefDO(BER_TLV_IE, tag=0x4f): class AidRefDO(BER_TLV_IE, tag=0x4f):
# SEID v1.1 Table 6-3 # SEID v1.1 Table 6-3
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class AidRefEmptyDO(BER_TLV_IE, tag=0xc0): class AidRefEmptyDO(BER_TLV_IE, tag=0xc0):
# SEID v1.1 Table 6-3 # SEID v1.1 Table 6-3
pass pass
class DevAppIdRefDO(BER_TLV_IE, tag=0xc1): class DevAppIdRefDO(BER_TLV_IE, tag=0xc1):
# SEID v1.1 Table 6-4 # SEID v1.1 Table 6-4
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class PkgRefDO(BER_TLV_IE, tag=0xca): class PkgRefDO(BER_TLV_IE, tag=0xca):
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
_construct = Struct('package_name_string'/GreedyString("ascii")) _construct = Struct('package_name_string'/GreedyString("ascii"))
class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO, AidRefEmptyDO, DevAppIdRefDO, PkgRefDO]): class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO, AidRefEmptyDO, DevAppIdRefDO, PkgRefDO]):
# SEID v1.1 Table 6-5 # SEID v1.1 Table 6-5
pass pass
class ApduArDO(BER_TLV_IE, tag=0xd0): class ApduArDO(BER_TLV_IE, tag=0xd0):
# SEID v1.1 Table 6-8 # SEID v1.1 Table 6-8
def _from_bytes(self, do: bytes): def _from_bytes(self, do: bytes):
@@ -76,6 +82,7 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
'mask': b2h(do[offset+4:offset+8])} 'mask': b2h(do[offset+4:offset+8])}
self.decoded = res self.decoded = res
return res return res
def _to_bytes(self): def _to_bytes(self):
if 'generic_access_rule' in self.decoded: if 'generic_access_rule' in self.decoded:
if self.decoded['generic_access_rule'] == 'never': if self.decoded['generic_access_rule'] == 'never':
@@ -99,94 +106,118 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
res += header_b + mask_b res += header_b + mask_b
return res return res
class NfcArDO(BER_TLV_IE, tag=0xd1): class NfcArDO(BER_TLV_IE, tag=0xd1):
# SEID v1.1 Table 6-9 # SEID v1.1 Table 6-9
_construct = Struct('nfc_event_access_rule'/Enum(Int8ub, never=0, always=1)) _construct = Struct('nfc_event_access_rule' /
Enum(Int8ub, never=0, always=1))
class PermArDO(BER_TLV_IE, tag=0xdb): class PermArDO(BER_TLV_IE, tag=0xdb):
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
_construct = Struct('permissions'/HexAdapter(Bytes(8))) _construct = Struct('permissions'/HexAdapter(Bytes(8)))
class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]): class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]):
# SEID v1.1 Table 6-7 # SEID v1.1 Table 6-7
pass pass
class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]): class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]):
# SEID v1.1 Table 6-6 # SEID v1.1 Table 6-6
pass pass
class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]): class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]):
# SEID v1.1 Table 4-2 # SEID v1.1 Table 4-2
pass pass
class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]): class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]):
# SEID v1.1 Table 4-3 # SEID v1.1 Table 4-3
pass pass
class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20): class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20):
# SEID v1.1 Table 4-4 # SEID v1.1 Table 4-4
_construct = Struct('refresh_tag'/HexAdapter(Bytes(8))) _construct = Struct('refresh_tag'/HexAdapter(Bytes(8)))
class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6): class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6):
# SEID v1.1 Table 6-12 # SEID v1.1 Table 6-12
_construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub) _construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub)
class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]): class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]):
# SEID v1.1 Table 6-10 # SEID v1.1 Table 6-10
pass pass
class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]): class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]):
# SEID v1.1 Table 5-14 # SEID v1.1 Table 5-14
pass pass
class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]): class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]):
# SEID v1.1 Table 6-11 # SEID v1.1 Table 6-11
pass pass
class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]): class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]):
# SEID v1.1 Table 4-5 # SEID v1.1 Table 4-5
pass pass
class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]): class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]):
# SEID v1.1 Table 5-2 # SEID v1.1 Table 5-2
pass pass
class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]): class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]):
# SEID v1.1 Table 5-4 # SEID v1.1 Table 5-4
pass pass
class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2): class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2):
# SEID V1.1 Table 5-6 # SEID V1.1 Table 5-6
pass pass
class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]): class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-7 # SEID v1.1 Table 5-7
pass pass
class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]): class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-8 # SEID v1.1 Table 5-8
pass pass
class CommandGetAll(BER_TLV_IE, tag=0xf4): class CommandGetAll(BER_TLV_IE, tag=0xf4):
# SEID v1.1 Table 5-9 # SEID v1.1 Table 5-9
pass pass
class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6): class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6):
# SEID v1.1 Table 5-10 # SEID v1.1 Table 5-10
pass pass
class CommandGetNext(BER_TLV_IE, tag=0xf5): class CommandGetNext(BER_TLV_IE, tag=0xf5):
# SEID v1.1 Table 5-11 # SEID v1.1 Table 5-11
pass pass
class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8): class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8):
# SEID v1.1 Table 5-12 # SEID v1.1 Table 5-12
pass pass
class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]): class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-13 # SEID v1.1 Table 5-13
pass pass
class BlockDO(BER_TLV_IE, tag=0xe7): class BlockDO(BER_TLV_IE, tag=0xe7):
# SEID v1.1 Table 6-13 # SEID v1.1 Table 6-13
_construct = Struct('offset'/Int16ub, 'length'/Int8ub) _construct = Struct('offset'/Int16ub, 'length'/Int8ub)
@@ -197,11 +228,15 @@ class GetCommandDoCollection(TLV_IE_Collection, nested=[RefDO, DeviceConfigDO]):
pass pass
# SEID v1.1 Table 4-2 # SEID v1.1 Table 4-2
class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO, class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO,
ResponseRefreshTagDO, ResponseAramConfigDO]): ResponseRefreshTagDO, ResponseAramConfigDO]):
pass pass
# SEID v1.1 Table 5-1 # SEID v1.1 Table 5-1
class StoreCommandDoCollection(TLV_IE_Collection, class StoreCommandDoCollection(TLV_IE_Collection,
nested=[BlockDO, CommandStoreRefArDO, CommandDelete, nested=[BlockDO, CommandStoreRefArDO, CommandDelete,
CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO, CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO,
@@ -215,6 +250,7 @@ class StoreResponseDoCollection(TLV_IE_Collection,
nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]): nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]):
pass pass
class ADF_ARAM(CardADF): class ADF_ARAM(CardADF):
def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None, def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None,
desc='ARA-M Application'): desc='ARA-M Application'):
@@ -259,7 +295,8 @@ class ADF_ARAM(CardADF):
@staticmethod @staticmethod
def get_config(tp, v_major=0, v_minor=0, v_patch=1): def get_config(tp, v_major=0, v_minor=0, v_patch=1):
cmd_do = DeviceConfigDO() cmd_do = DeviceConfigDO()
cmd_do.from_dict([{'DeviceInterfaceVersionDO': {'major': v_major, 'minor': v_minor, 'patch': v_patch }}]) cmd_do.from_dict([{'DeviceInterfaceVersionDO': {
'major': v_major, 'minor': v_minor, 'patch': v_patch}}])
return ADF_ARAM.xceive_apdu_tlv(tp, '80cadf21', cmd_do, ResponseAramConfigDO) return ADF_ARAM.xceive_apdu_tlv(tp, '80cadf21', cmd_do, ResponseAramConfigDO)
@with_default_category('Application-Specific Commands') @with_default_category('Application-Specific Commands')
@@ -281,20 +318,30 @@ class ADF_ARAM(CardADF):
store_ref_ar_do_parse = argparse.ArgumentParser() store_ref_ar_do_parse = argparse.ArgumentParser()
# REF-DO # REF-DO
store_ref_ar_do_parse.add_argument('--device-app-id', required=True, help='Identifies the specific device application that the rule appplies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)') store_ref_ar_do_parse.add_argument(
'--device-app-id', required=True, help='Identifies the specific device application that the rule appplies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)')
aid_grp = store_ref_ar_do_parse.add_mutually_exclusive_group() aid_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
aid_grp.add_argument('--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 hex bytes)') aid_grp.add_argument(
aid_grp.add_argument('--aid-empty', action='store_true', help='No specific SE application, applies to all applications') '--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 hex bytes)')
store_ref_ar_do_parse.add_argument('--pkg-ref', help='Full Android Java package name (up to 127 chars ASCII)') aid_grp.add_argument('--aid-empty', action='store_true',
help='No specific SE application, applies to all applications')
store_ref_ar_do_parse.add_argument(
'--pkg-ref', help='Full Android Java package name (up to 127 chars ASCII)')
# AR-DO # AR-DO
apdu_grp = store_ref_ar_do_parse.add_mutually_exclusive_group() apdu_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
apdu_grp.add_argument('--apdu-never', action='store_true', help='APDU access is not allowed') apdu_grp.add_argument(
apdu_grp.add_argument('--apdu-always', action='store_true', help='APDU access is allowed') '--apdu-never', action='store_true', help='APDU access is not allowed')
apdu_grp.add_argument('--apdu-filter', help='APDU filter: 4 byte CLA/INS/P1/P2 followed by 4 byte mask (8 hex bytes)') apdu_grp.add_argument(
'--apdu-always', action='store_true', help='APDU access is allowed')
apdu_grp.add_argument(
'--apdu-filter', help='APDU filter: 4 byte CLA/INS/P1/P2 followed by 4 byte mask (8 hex bytes)')
nfc_grp = store_ref_ar_do_parse.add_mutually_exclusive_group() nfc_grp = store_ref_ar_do_parse.add_mutually_exclusive_group()
nfc_grp.add_argument('--nfc-always', action='store_true', help='NFC event access is allowed') nfc_grp.add_argument('--nfc-always', action='store_true',
nfc_grp.add_argument('--nfc-never', action='store_true', help='NFC event access is not allowed') help='NFC event access is allowed')
store_ref_ar_do_parse.add_argument('--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)') nfc_grp.add_argument('--nfc-never', action='store_true',
help='NFC event access is not allowed')
store_ref_ar_do_parse.add_argument(
'--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)')
@cmd2.with_argparser(store_ref_ar_do_parse) @cmd2.with_argparser(store_ref_ar_do_parse)
def do_aram_store_ref_ar_do(self, opts): def do_aram_store_ref_ar_do(self, opts):
@@ -359,6 +406,7 @@ sw_aram = {
} }
} }
class CardApplicationARAM(CardApplication): class CardApplicationARAM(CardApplication):
def __init__(self): def __init__(self):
super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram) super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram)

View File

@@ -30,6 +30,7 @@ import subprocess
import sys import sys
import yaml import yaml
class CardHandlerBase: class CardHandlerBase:
"""Abstract base class representing a mechanism for card insertion/removal.""" """Abstract base class representing a mechanism for card insertion/removal."""
@@ -116,7 +117,8 @@ class CardHandlerAuto(CardHandlerBase):
def __exec_cmd(self, command): def __exec_cmd(self, command):
print("Card handler Commandline: " + str(command)) print("Card handler Commandline: " + str(command))
proc = subprocess.Popen([command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) proc = subprocess.Popen(
[command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
out = proc.communicate() out = proc.communicate()
rc = proc.returncode rc = proc.returncode

View File

@@ -35,10 +35,12 @@ import csv
card_key_providers = [] # type: List['CardKeyProvider'] card_key_providers = [] # type: List['CardKeyProvider']
class CardKeyProvider(abc.ABC): class CardKeyProvider(abc.ABC):
"""Base class, not containing any concrete implementation.""" """Base class, not containing any concrete implementation."""
VALID_FIELD_NAMES = ['ICCID', 'ADM1', 'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2'] VALID_FIELD_NAMES = ['ICCID', 'ADM1',
'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2']
# check input parameters, but do nothing concrete yet # check input parameters, but do nothing concrete yet
def _verify_get_data(self, fields: List[str] = [], key: str = 'ICCID', value: str = "") -> Dict[str, str]: def _verify_get_data(self, fields: List[str] = [], key: str = 'ICCID', value: str = "") -> Dict[str, str]:
@@ -80,6 +82,7 @@ class CardKeyProvider(abc.ABC):
dictionary of {field, value} strings for each requested field from 'fields' dictionary of {field, value} strings for each requested field from 'fields'
""" """
class CardKeyProviderCsv(CardKeyProvider): class CardKeyProviderCsv(CardKeyProvider):
"""Card key provider implementation that allows to query against a specified CSV file""" """Card key provider implementation that allows to query against a specified CSV file"""
csv_file = None csv_file = None
@@ -101,7 +104,8 @@ class CardKeyProviderCsv(CardKeyProvider):
self.csv_file.seek(0) self.csv_file.seek(0)
cr = csv.DictReader(self.csv_file) cr = csv.DictReader(self.csv_file)
if not cr: if not cr:
raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename) raise RuntimeError(
"Could not open DictReader for CSV-File '%s'" % self.filename)
cr.fieldnames = [field.upper() for field in cr.fieldnames] cr.fieldnames = [field.upper() for field in cr.fieldnames]
rc = {} rc = {}
@@ -141,7 +145,8 @@ def card_key_provider_get(fields, key:str, value:str, provider_list=card_key_pro
""" """
for p in provider_list: for p in provider_list:
if not isinstance(p, CardKeyProvider): if not isinstance(p, CardKeyProvider):
raise ValueError("provider list contains element which is not a card data provier") raise ValueError(
"provider list contains element which is not a card data provier")
result = p.get(fields, key, value) result = p.get(fields, key, value)
if result: if result:
return result return result
@@ -161,7 +166,8 @@ def card_key_provider_get_field(field:str, key:str, value:str, provider_list=car
""" """
for p in provider_list: for p in provider_list:
if not isinstance(p, CardKeyProvider): if not isinstance(p, CardKeyProvider):
raise ValueError("provider list contains element which is not a card data provier") raise ValueError(
"provider list contains element which is not a card data provier")
result = p.get_field(field, key, value) result = p.get_field(field, key, value)
if result: if result:
return result return result

View File

@@ -32,6 +32,7 @@ from pySim.utils import *
from smartcard.util import toBytes from smartcard.util import toBytes
from pytlv.TLV import * from pytlv.TLV import *
def format_addr(addr: str, addr_type: str) -> str: def format_addr(addr: str, addr_type: str) -> str:
""" """
helper function to format an FQDN (addr_type = '00') or IPv4 helper function to format an FQDN (addr_type = '00') or IPv4
@@ -50,6 +51,7 @@ def format_addr(addr:str, addr_type:str) -> str:
res += "\t%s # %s\n" % (addr_hex, addr) res += "\t%s # %s\n" % (addr_hex, addr)
return res return res
class SimCard(object): class SimCard(object):
name = 'SIM' name = 'SIM'
@@ -126,7 +128,8 @@ class SimCard(object):
size = len(data[0]) // 2 size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc) hplmn = enc_plmn(mcc, mnc)
content = hplmn + access_tech content = hplmn + access_tech
data, sw = self._scc.update_binary(EF['HPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) data, sw = self._scc.update_binary(
EF['HPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
return sw return sw
def read_oplmn_act(self): def read_oplmn_act(self):
@@ -142,7 +145,8 @@ class SimCard(object):
size = len(data[0]) // 2 size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc) hplmn = enc_plmn(mcc, mnc)
content = hplmn + access_tech content = hplmn + access_tech
data, sw = self._scc.update_binary(EF['OPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) data, sw = self._scc.update_binary(
EF['OPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
return sw return sw
def read_plmn_act(self): def read_plmn_act(self):
@@ -158,14 +162,16 @@ class SimCard(object):
size = len(data[0]) // 2 size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc) hplmn = enc_plmn(mcc, mnc)
content = hplmn + access_tech content = hplmn + access_tech
data, sw = self._scc.update_binary(EF['PLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) data, sw = self._scc.update_binary(
EF['PLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
return sw return sw
def update_plmnsel(self, mcc, mnc): def update_plmnsel(self, mcc, mnc):
data = self._scc.read_binary(EF['PLMNsel'], length=None, offset=0) data = self._scc.read_binary(EF['PLMNsel'], length=None, offset=0)
size = len(data[0]) // 2 size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc) hplmn = enc_plmn(mcc, mnc)
data, sw = self._scc.update_binary(EF['PLMNsel'], hplmn + 'ff' * (size-3)) data, sw = self._scc.update_binary(
EF['PLMNsel'], hplmn + 'ff' * (size-3))
return sw return sw
def update_smsp(self, smsp): def update_smsp(self, smsp):
@@ -193,7 +199,8 @@ class SimCard(object):
ad = EF_AD() ad = EF_AD()
# read from card # read from card
raw_hex_data, sw = self._scc.read_binary(EF['AD'], length=None, offset=0) raw_hex_data, sw = self._scc.read_binary(
EF['AD'], length=None, offset=0)
abstract_data = ad.decode_hex(raw_hex_data) abstract_data = ad.decode_hex(raw_hex_data)
# perform updates # perform updates
@@ -321,7 +328,8 @@ class SimCard(object):
def erase_record(self, ef, rec_no): def erase_record(self, ef, rec_no):
"""Erase the contents of a single record""" """Erase the contents of a single record"""
len = self._scc.record_size(ef) len = self._scc.record_size(ef)
self._scc.update_record(ef, rec_no, "ff" * len, force_len=False, verify=True) self._scc.update_record(ef, rec_no, "ff" * len,
force_len=False, verify=True)
def set_apdu_parameter(self, cla, sel_ctrl): def set_apdu_parameter(self, cla, sel_ctrl):
"""Set apdu parameters (class byte and selection control bytes)""" """Set apdu parameters (class byte and selection control bytes)"""
@@ -332,6 +340,7 @@ class SimCard(object):
"""Get apdu parameters (class byte and selection control bytes)""" """Get apdu parameters (class byte and selection control bytes)"""
return (self._scc.cla_byte, self._scc.sel_ctrl) return (self._scc.cla_byte, self._scc.sel_ctrl)
class UsimCard(SimCard): class UsimCard(SimCard):
name = 'USIM' name = 'USIM'
@@ -347,7 +356,8 @@ class UsimCard(SimCard):
return (None, sw) return (None, sw)
def update_ehplmn(self, mcc, mnc): def update_ehplmn(self, mcc, mnc):
data = self._scc.read_binary(EF_USIM_ADF_map['EHPLMN'], length=None, offset=0) data = self._scc.read_binary(
EF_USIM_ADF_map['EHPLMN'], length=None, offset=0)
size = len(data[0]) // 2 size = len(data[0]) // 2
ehplmn = enc_plmn(mcc, mnc) ehplmn = enc_plmn(mcc, mnc)
data, sw = self._scc.update_binary(EF_USIM_ADF_map['EHPLMN'], ehplmn) data, sw = self._scc.update_binary(EF_USIM_ADF_map['EHPLMN'], ehplmn)
@@ -370,7 +380,8 @@ class UsimCard(SimCard):
if len(epdgid) > 0: if len(epdgid) > 0:
addr_type = get_addr_type(epdgid) addr_type = get_addr_type(epdgid)
if addr_type == None: if addr_type == None:
raise ValueError("Unknown ePDG Id address type or invalid address provided") raise ValueError(
"Unknown ePDG Id address type or invalid address provided")
epdgid_tlv = rpad(enc_addr_tlv(epdgid, ('%02x' % addr_type)), size) epdgid_tlv = rpad(enc_addr_tlv(epdgid, ('%02x' % addr_type)), size)
else: else:
epdgid_tlv = rpad('ff', size) epdgid_tlv = rpad('ff', size)
@@ -386,13 +397,16 @@ class UsimCard(SimCard):
return (None, sw) return (None, sw)
def update_ePDGSelection(self, mcc, mnc): def update_ePDGSelection(self, mcc, mnc):
(res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGSelection'], length=None, offset=0) (res, sw) = self._scc.read_binary(
EF_USIM_ADF_map['ePDGSelection'], length=None, offset=0)
if sw == '9000' and (len(mcc) == 0 or len(mnc) == 0): if sw == '9000' and (len(mcc) == 0 or len(mnc) == 0):
# Reset contents # Reset contents
# 80 - Tag value # 80 - Tag value
(res, sw) = self._scc.update_binary(EF_USIM_ADF_map['ePDGSelection'], rpad('', len(res))) (res, sw) = self._scc.update_binary(
EF_USIM_ADF_map['ePDGSelection'], rpad('', len(res)))
elif sw == '9000': elif sw == '9000':
(res, sw) = self._scc.update_binary(EF_USIM_ADF_map['ePDGSelection'], enc_ePDGSelection(res, mcc, mnc)) (res, sw) = self._scc.update_binary(
EF_USIM_ADF_map['ePDGSelection'], enc_ePDGSelection(res, mcc, mnc))
return sw return sw
def read_ust(self): def read_ust(self):
@@ -407,9 +421,11 @@ class UsimCard(SimCard):
(res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST']) (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST'])
if sw == '9000': if sw == '9000':
content = enc_st(res, service, bit) content = enc_st(res, service, bit)
(res, sw) = self._scc.update_binary(EF_USIM_ADF_map['UST'], content) (res, sw) = self._scc.update_binary(
EF_USIM_ADF_map['UST'], content)
return sw return sw
class IsimCard(SimCard): class IsimCard(SimCard):
name = 'ISIM' name = 'ISIM'
@@ -429,29 +445,33 @@ class IsimCard(SimCard):
addr = None addr = None
addr_type = None addr_type = None
content = format_addr(addr, addr_type) content = format_addr(addr, addr_type)
pcscf_recs += "%s" % (len(content) and content or '\tNot available\n') pcscf_recs += "%s" % (len(content)
and content or '\tNot available\n')
else: else:
pcscf_recs += "\tP-CSCF: Can't read, response code = %s\n" % (sw) pcscf_recs += "\tP-CSCF: Can't read, response code = %s\n" % (
sw)
return pcscf_recs return pcscf_recs
def update_pcscf(self, pcscf): def update_pcscf(self, pcscf):
if len(pcscf) > 0: if len(pcscf) > 0:
addr_type = get_addr_type(pcscf) addr_type = get_addr_type(pcscf)
if addr_type == None: if addr_type == None:
raise ValueError("Unknown PCSCF address type or invalid address provided") raise ValueError(
"Unknown PCSCF address type or invalid address provided")
content = enc_addr_tlv(pcscf, ('%02x' % addr_type)) content = enc_addr_tlv(pcscf, ('%02x' % addr_type))
else: else:
# Just the tag value # Just the tag value
content = '80' content = '80'
rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['PCSCF']) rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['PCSCF'])
pcscf_tlv = rpad(content, rec_size_bytes*2) pcscf_tlv = rpad(content, rec_size_bytes*2)
data, sw = self._scc.update_record(EF_ISIM_ADF_map['PCSCF'], 1, pcscf_tlv) data, sw = self._scc.update_record(
EF_ISIM_ADF_map['PCSCF'], 1, pcscf_tlv)
return sw return sw
def read_domain(self): def read_domain(self):
(res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['DOMAIN']) (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['DOMAIN'])
if sw == '9000': if sw == '9000':
# Skip the inital tag value ('80') byte and get length of contents # Skip the initial tag value ('80') byte and get length of contents
length = int(res[2:4], 16) length = int(res[2:4], 16)
content = h2s(res[4:4+(length*2)]) content = h2s(res[4:4+(length*2)])
return (content, sw) return (content, sw)
@@ -472,13 +492,14 @@ class IsimCard(SimCard):
content = tlv.build({'80': hex_str}) content = tlv.build({'80': hex_str})
bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['DOMAIN']) bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['DOMAIN'])
data, sw = self._scc.update_binary(EF_ISIM_ADF_map['DOMAIN'], rpad(content, bin_size_bytes*2)) data, sw = self._scc.update_binary(
EF_ISIM_ADF_map['DOMAIN'], rpad(content, bin_size_bytes*2))
return sw return sw
def read_impi(self): def read_impi(self):
(res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IMPI']) (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IMPI'])
if sw == '9000': if sw == '9000':
# Skip the inital tag value ('80') byte and get length of contents # Skip the initial tag value ('80') byte and get length of contents
length = int(res[2:4], 16) length = int(res[2:4], 16)
content = h2s(res[4:4+(length*2)]) content = h2s(res[4:4+(length*2)])
return (content, sw) return (content, sw)
@@ -494,7 +515,8 @@ class IsimCard(SimCard):
content = tlv.build({'80': hex_str}) content = tlv.build({'80': hex_str})
bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['IMPI']) bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['IMPI'])
data, sw = self._scc.update_binary(EF_ISIM_ADF_map['IMPI'], rpad(content, bin_size_bytes*2)) data, sw = self._scc.update_binary(
EF_ISIM_ADF_map['IMPI'], rpad(content, bin_size_bytes*2))
return sw return sw
def read_impu(self): def read_impu(self):
@@ -503,12 +525,14 @@ class IsimCard(SimCard):
for i in range(0, rec_cnt): for i in range(0, rec_cnt):
(res, sw) = self._scc.read_record(EF_ISIM_ADF_map['IMPU'], i + 1) (res, sw) = self._scc.read_record(EF_ISIM_ADF_map['IMPU'], i + 1)
if sw == '9000': if sw == '9000':
# Skip the inital tag value ('80') byte and get length of contents # Skip the initial tag value ('80') byte and get length of contents
length = int(res[2:4], 16) length = int(res[2:4], 16)
content = h2s(res[4:4+(length*2)]) content = h2s(res[4:4+(length*2)])
impu_recs += "\t%s\n" % (len(content) and content or 'Not available') impu_recs += "\t%s\n" % (len(content)
and content or 'Not available')
else: else:
impu_recs += "IMS public user identity: Can't read, response code = %s\n" % (sw) impu_recs += "IMS public user identity: Can't read, response code = %s\n" % (
sw)
return impu_recs return impu_recs
def update_impu(self, impu=None): def update_impu(self, impu=None):
@@ -521,23 +545,28 @@ class IsimCard(SimCard):
rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['IMPU']) rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['IMPU'])
impu_tlv = rpad(content, rec_size_bytes*2) impu_tlv = rpad(content, rec_size_bytes*2)
data, sw = self._scc.update_record(EF_ISIM_ADF_map['IMPU'], 1, impu_tlv) data, sw = self._scc.update_record(
EF_ISIM_ADF_map['IMPU'], 1, impu_tlv)
return sw return sw
def read_iari(self): def read_iari(self):
rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['UICCIARI']) rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['UICCIARI'])
uiari_recs = "" uiari_recs = ""
for i in range(0, rec_cnt): for i in range(0, rec_cnt):
(res, sw) = self._scc.read_record(EF_ISIM_ADF_map['UICCIARI'], i + 1) (res, sw) = self._scc.read_record(
EF_ISIM_ADF_map['UICCIARI'], i + 1)
if sw == '9000': if sw == '9000':
# Skip the inital tag value ('80') byte and get length of contents # Skip the initial tag value ('80') byte and get length of contents
length = int(res[2:4], 16) length = int(res[2:4], 16)
content = h2s(res[4:4+(length*2)]) content = h2s(res[4:4+(length*2)])
uiari_recs += "\t%s\n" % (len(content) and content or 'Not available') uiari_recs += "\t%s\n" % (len(content)
and content or 'Not available')
else: else:
uiari_recs += "UICC IARI: Can't read, response code = %s\n" % (sw) uiari_recs += "UICC IARI: Can't read, response code = %s\n" % (
sw)
return uiari_recs return uiari_recs
class MagicSimBase(abc.ABC, SimCard): class MagicSimBase(abc.ABC, SimCard):
""" """
Theses cards uses several record based EFs to store the provider infos, Theses cards uses several record based EFs to store the provider infos,
@@ -600,7 +629,8 @@ class MagicSimBase(abc.ABC, SimCard):
# Operator name ( 3f00/7f4d/8f0c ) # Operator name ( 3f00/7f4d/8f0c )
self._scc.update_record(self._files['name'][0], 2, self._scc.update_record(self._files['name'][0], 2,
rpad(b2h(p['name']), 32) + ('%02x' % len(p['name'])) + '01' rpad(b2h(p['name']), 32) + ('%02x' %
len(p['name'])) + '01'
) )
# ICCID/IMSI/Ki/HPLMN ( 3f00/7f4d/8f0d ) # ICCID/IMSI/Ki/HPLMN ( 3f00/7f4d/8f0d )
@@ -830,6 +860,7 @@ class SysmoSIMgr1(GrcardSim):
return None return None
return None return None
class SysmoUSIMgr1(UsimCard): class SysmoUSIMgr1(UsimCard):
""" """
sysmocom sysmoUSIM-GR1 sysmocom sysmoUSIM-GR1
@@ -845,7 +876,8 @@ class SysmoUSIMgr1(UsimCard):
# TODO: check if verify_chv could be used or what it needs # TODO: check if verify_chv could be used or what it needs
# self._scc.verify_chv(0x0A, [0x33,0x32,0x32,0x31,0x33,0x32,0x33,0x32]) # self._scc.verify_chv(0x0A, [0x33,0x32,0x32,0x31,0x33,0x32,0x33,0x32])
# Unlock the card.. # Unlock the card..
data, sw = self._scc._tp.send_apdu_checksw("0020000A083332323133323332") data, sw = self._scc._tp.send_apdu_checksw(
"0020000A083332323133323332")
# TODO: move into SimCardCommands # TODO: move into SimCardCommands
par = (p['ki'] + # 16b K par = (p['ki'] + # 16b K
@@ -956,7 +988,8 @@ class SysmoUSIMSJS1(UsimCard):
def verify_adm(self, key): def verify_adm(self, key):
# authenticate as ADM using default key (written on the card..) # authenticate as ADM using default key (written on the card..)
if not key: if not key:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
(res, sw) = self._scc.verify_chv(0x0A, key) (res, sw) = self._scc.verify_chv(0x0A, key)
return sw return sw
@@ -1027,7 +1060,8 @@ class SysmoUSIMSJS1(UsimCard):
# EF.SMSP # EF.SMSP
if p.get('smsp'): if p.get('smsp'):
r = self._scc.select_path(['3f00', '7f10']) r = self._scc.select_path(['3f00', '7f10'])
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True) data, sw = self._scc.update_record(
'6f42', 1, lpad(p['smsp'], 104), force_len=True)
# EF.MSISDN # EF.MSISDN
# TODO: Alpha Identifier (currently 'ff'O * 20) # TODO: Alpha Identifier (currently 'ff'O * 20)
@@ -1069,7 +1103,6 @@ class FairwavesSIM(UsimCard):
self._adm_chv_num = 0x11 self._adm_chv_num = 0x11
self._adm2_chv_num = 0x12 self._adm2_chv_num = 0x12
@classmethod @classmethod
def autodetect(kls, scc): def autodetect(kls, scc):
try: try:
@@ -1080,7 +1113,6 @@ class FairwavesSIM(UsimCard):
return None return None
return None return None
def verify_adm2(self, key): def verify_adm2(self, key):
''' '''
Authenticate with ADM2 key. Authenticate with ADM2 key.
@@ -1093,7 +1125,6 @@ class FairwavesSIM(UsimCard):
(res, sw) = self._scc.verify_chv(self._adm2_chv_num, key) (res, sw) = self._scc.verify_chv(self._adm2_chv_num, key)
return sw return sw
def read_ki(self): def read_ki(self):
""" """
Read Ki in proprietary file. Read Ki in proprietary file.
@@ -1102,7 +1133,6 @@ class FairwavesSIM(UsimCard):
""" """
return self._scc.read_binary(self._EF['Ki']) return self._scc.read_binary(self._EF['Ki'])
def update_ki(self, ki): def update_ki(self, ki):
""" """
Set Ki in proprietary file. Set Ki in proprietary file.
@@ -1112,7 +1142,6 @@ class FairwavesSIM(UsimCard):
data, sw = self._scc.update_binary(self._EF['Ki'], ki) data, sw = self._scc.update_binary(self._EF['Ki'], ki)
return sw return sw
def read_op_opc(self): def read_op_opc(self):
""" """
Read Ki in proprietary file. Read Ki in proprietary file.
@@ -1123,7 +1152,6 @@ class FairwavesSIM(UsimCard):
type = 'OP' if ef[0:2] == '00' else 'OPC' type = 'OP' if ef[0:2] == '00' else 'OPC'
return ((type, ef[2:]), sw) return ((type, ef[2:]), sw)
def update_op(self, op): def update_op(self, op):
""" """
Set OP in proprietary file. Set OP in proprietary file.
@@ -1134,7 +1162,6 @@ class FairwavesSIM(UsimCard):
data, sw = self._scc.update_binary(self._EF['OP/OPC'], content) data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
return sw return sw
def update_opc(self, opc): def update_opc(self, opc):
""" """
Set OPC in proprietary file. Set OPC in proprietary file.
@@ -1166,7 +1193,8 @@ class FairwavesSIM(UsimCard):
def _program(self, p): def _program(self, p):
# authenticate as ADM1 # authenticate as ADM1
if not p['pin_adm']: if not p['pin_adm']:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
self.verify_adm(h2b(p['pin_adm'])) self.verify_adm(h2b(p['pin_adm']))
# TODO: Set operator name # TODO: Set operator name
@@ -1196,6 +1224,7 @@ class FairwavesSIM(UsimCard):
if sw != '9000': if sw != '9000':
print("Programming ACC failed with code %s" % sw) print("Programming ACC failed with code %s" % sw)
class OpenCellsSim(SimCard): class OpenCellsSim(SimCard):
""" """
OpenCellsSim OpenCellsSim
@@ -1208,7 +1237,6 @@ class OpenCellsSim(SimCard):
super(OpenCellsSim, self).__init__(ssc) super(OpenCellsSim, self).__init__(ssc)
self._adm_chv_num = 0x0A self._adm_chv_num = 0x0A
@classmethod @classmethod
def autodetect(kls, scc): def autodetect(kls, scc):
try: try:
@@ -1219,10 +1247,10 @@ class OpenCellsSim(SimCard):
return None return None
return None return None
def program(self, p): def program(self, p):
if not p['pin_adm']: if not p['pin_adm']:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
self._scc.verify_chv(0x0A, h2b(p['pin_adm'])) self._scc.verify_chv(0x0A, h2b(p['pin_adm']))
# select MF # select MF
@@ -1245,6 +1273,7 @@ class OpenCellsSim(SimCard):
# write EF.IMSI # write EF.IMSI
data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
class WavemobileSim(UsimCard): class WavemobileSim(UsimCard):
""" """
WavemobileSim WavemobileSim
@@ -1271,23 +1300,27 @@ class WavemobileSim(UsimCard):
def program(self, p): def program(self, p):
if not p['pin_adm']: if not p['pin_adm']:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
self.verify_adm(h2b(p['pin_adm'])) self.verify_adm(h2b(p['pin_adm']))
# EF.ICCID # EF.ICCID
# TODO: Add programming of the ICCID # TODO: Add programming of the ICCID
if p.get('iccid'): if p.get('iccid'):
print("Warning: Programming of the ICCID is not implemented for this type of card.") print(
"Warning: Programming of the ICCID is not implemented for this type of card.")
# KI (Presumably a propritary file) # KI (Presumably a propritary file)
# TODO: Add programming of KI # TODO: Add programming of KI
if p.get('ki'): if p.get('ki'):
print("Warning: Programming of the KI is not implemented for this type of card.") print(
"Warning: Programming of the KI is not implemented for this type of card.")
# OPc (Presumably a propritary file) # OPc (Presumably a propritary file)
# TODO: Add programming of OPc # TODO: Add programming of OPc
if p.get('opc'): if p.get('opc'):
print("Warning: Programming of the OPc is not implemented for this type of card.") print(
"Warning: Programming of the OPc is not implemented for this type of card.")
# EF.SMSP # EF.SMSP
if p.get('smsp'): if p.get('smsp'):
@@ -1374,7 +1407,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
def verify_adm(self, key): def verify_adm(self, key):
# authenticate as ADM using default key (written on the card..) # authenticate as ADM using default key (written on the card..)
if not key: if not key:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
(res, sw) = self._scc.verify_chv(0x0A, key) (res, sw) = self._scc.verify_chv(0x0A, key)
return sw return sw
@@ -1386,7 +1420,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
# license management, so the ICCID must be kept at its factory # license management, so the ICCID must be kept at its factory
# setting! # setting!
if p.get('iccid'): if p.get('iccid'):
print("Warning: Programming of the ICCID is not implemented for this type of card.") print(
"Warning: Programming of the ICCID is not implemented for this type of card.")
# select DF_GSM # select DF_GSM
self._scc.select_path(['7f20']) self._scc.select_path(['7f20'])
@@ -1436,7 +1471,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
# EF.SMSP # EF.SMSP
if p.get('smsp'): if p.get('smsp'):
r = self._scc.select_path(['3f00', '7f10']) r = self._scc.select_path(['3f00', '7f10'])
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True) data, sw = self._scc.update_record(
'6f42', 1, lpad(p['smsp'], 104), force_len=True)
# EF.MSISDN # EF.MSISDN
# TODO: Alpha Identifier (currently 'ff'O * 20) # TODO: Alpha Identifier (currently 'ff'O * 20)
@@ -1447,7 +1483,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
content = 'ff' * 20 + msisdn content = 'ff' * 20 + msisdn
r = self._scc.select_path(['3f00', '7f10']) r = self._scc.select_path(['3f00', '7f10'])
data, sw = self._scc.update_record('6F40', 1, content, force_len=True) data, sw = self._scc.update_record(
'6F40', 1, content, force_len=True)
# EF.ACC # EF.ACC
if p.get('acc'): if p.get('acc'):
@@ -1484,7 +1521,6 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
if sw != '9000': if sw != '9000':
print("Programming P-CSCF failed with code %s" % sw) print("Programming P-CSCF failed with code %s" % sw)
# update EF.DOMAIN in ADF.ISIM # update EF.DOMAIN in ADF.ISIM
if self.file_exists(EF_ISIM_ADF_map['DOMAIN']): if self.file_exists(EF_ISIM_ADF_map['DOMAIN']):
if p.get('ims_hdomain'): if p.get('ims_hdomain'):
@@ -1493,7 +1529,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
sw = self.update_domain() sw = self.update_domain()
if sw != '9000': if sw != '9000':
print("Programming Home Network Domain Name failed with code %s"%sw) print(
"Programming Home Network Domain Name failed with code %s" % sw)
# update EF.IMPI in ADF.ISIM # update EF.IMPI in ADF.ISIM
# TODO: Validate IMPI input # TODO: Validate IMPI input
@@ -1544,13 +1581,13 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
if self.file_exists(EF_USIM_ADF_map['ePDGSelection']): if self.file_exists(EF_USIM_ADF_map['ePDGSelection']):
if p.get('epdgSelection'): if p.get('epdgSelection'):
epdg_plmn = p['epdgSelection'] epdg_plmn = p['epdgSelection']
sw = self.update_ePDGSelection(epdg_plmn[:3], epdg_plmn[3:]) sw = self.update_ePDGSelection(
epdg_plmn[:3], epdg_plmn[3:])
else: else:
sw = self.update_ePDGSelection("", "") sw = self.update_ePDGSelection("", "")
if sw != '9000': if sw != '9000':
print("Programming ePDGSelection failed with code %s" % sw) print("Programming ePDGSelection failed with code %s" % sw)
# After successfully programming EF.ePDGId and EF.ePDGSelection, # After successfully programming EF.ePDGId and EF.ePDGSelection,
# Set service 106 and 107 as available in EF.UST # Set service 106 and 107 as available in EF.UST
# Disable service 95, 99, 115 if ISIM application is present # Disable service 95, 99, 115 if ISIM application is present
@@ -1581,6 +1618,7 @@ _cards_classes = [ FakeMagicSim, SuperSim, MagicSim, GrcardSim,
SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1, SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1,
FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2] FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2]
def card_detect(ctype, scc): def card_detect(ctype, scc):
# Detect type if needed # Detect type if needed
card = None card = None

View File

@@ -24,34 +24,48 @@ from construct import *
# Tag values as per TS 101 220 Table 7.23 # Tag values as per TS 101 220 Table 7.23
# TS 102 223 Section 8.1 # TS 102 223 Section 8.1
class Address(COMPR_TLV_IE, tag=0x06): class Address(COMPR_TLV_IE, tag=0x06):
_construct = Struct('ton_npi'/Int8ub, _construct = Struct('ton_npi'/Int8ub,
'call_number'/BcdAdapter(Bytes(this._.total_len-1))) 'call_number'/BcdAdapter(Bytes(this._.total_len-1)))
# TS 102 223 Section 8.2 # TS 102 223 Section 8.2
class AlphaIdentifier(COMPR_TLV_IE, tag=0x05): class AlphaIdentifier(COMPR_TLV_IE, tag=0x05):
# FIXME: like EF.ADN # FIXME: like EF.ADN
pass pass
# TS 102 223 Section 8.3 # TS 102 223 Section 8.3
class Subaddress(COMPR_TLV_IE, tag=0x08): class Subaddress(COMPR_TLV_IE, tag=0x08):
pass pass
# TS 102 223 Section 8.4 # TS 102 223 Section 8.4
class CapabilityConfigParams(COMPR_TLV_IE, tag=0x07): class CapabilityConfigParams(COMPR_TLV_IE, tag=0x07):
pass pass
# TS 31.111 Section 8.5 # TS 31.111 Section 8.5
class CBSPage(COMPR_TLV_IE, tag=0x0C): class CBSPage(COMPR_TLV_IE, tag=0x0C):
pass pass
# TS 102 223 Section 8.6 # TS 102 223 Section 8.6
class CommandDetails(COMPR_TLV_IE, tag=0x01): class CommandDetails(COMPR_TLV_IE, tag=0x01):
_construct = Struct('command_number'/Int8ub, _construct = Struct('command_number'/Int8ub,
'type_of_command'/Int8ub, 'type_of_command'/Int8ub,
'command_qualifier'/Int8ub) 'command_qualifier'/Int8ub)
# TS 102 223 Section 8.7 # TS 102 223 Section 8.7
class DeviceIdentities(COMPR_TLV_IE, tag=0x82): class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
DEV_IDS = bidict({ DEV_IDS = bidict({
0x01: 'keypad', 0x01: 'keypad',
@@ -91,6 +105,7 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
0x82: 'terminal', 0x82: 'terminal',
0x83: 'network', 0x83: 'network',
}) })
def _from_bytes(self, do: bytes): def _from_bytes(self, do: bytes):
return {'source_dev_id': self.DEV_IDS[do[0]], 'dest_dev_id': self.DEV_IDS[do[1]]} return {'source_dev_id': self.DEV_IDS[do[0]], 'dest_dev_id': self.DEV_IDS[do[1]]}
@@ -100,65 +115,84 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
return bytes([src, dst]) return bytes([src, dst])
# TS 102 223 Section 8.8 # TS 102 223 Section 8.8
class Duration(COMPR_TLV_IE, tag=0x04): class Duration(COMPR_TLV_IE, tag=0x04):
_construct = Struct('time_unit'/Int8ub, _construct = Struct('time_unit'/Int8ub,
'time_interval'/Int8ub) 'time_interval'/Int8ub)
# TS 102 223 Section 8.9 # TS 102 223 Section 8.9
class Item(COMPR_TLV_IE, tag=0x0f): class Item(COMPR_TLV_IE, tag=0x0f):
_construct = Struct('identifier'/Int8ub, _construct = Struct('identifier'/Int8ub,
'text_string'/GsmStringAdapter(GreedyBytes)) 'text_string'/GsmStringAdapter(GreedyBytes))
# TS 102 223 Section 8.10 # TS 102 223 Section 8.10
class ItemIdentifier(COMPR_TLV_IE, tag=0x10): class ItemIdentifier(COMPR_TLV_IE, tag=0x10):
_construct = Struct('identifier'/Int8ub) _construct = Struct('identifier'/Int8ub)
# TS 102 223 Section 8.11 # TS 102 223 Section 8.11
class ResponseLength(COMPR_TLV_IE, tag=0x11): class ResponseLength(COMPR_TLV_IE, tag=0x11):
_construct = Struct('minimum_length'/Int8ub, _construct = Struct('minimum_length'/Int8ub,
'maximum_length'/Int8ub) 'maximum_length'/Int8ub)
# TS 102 223 Section 8.12 # TS 102 223 Section 8.12
class Result(COMPR_TLV_IE, tag=0x03): class Result(COMPR_TLV_IE, tag=0x03):
_construct = Struct('general_result'/Int8ub, _construct = Struct('general_result'/Int8ub,
'additional_information'/HexAdapter(GreedyBytes)) 'additional_information'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.13 + TS 31.111 Section 8.13 # TS 102 223 Section 8.13 + TS 31.111 Section 8.13
class SMS_TPDU(COMPR_TLV_IE, tag=0x8B): class SMS_TPDU(COMPR_TLV_IE, tag=0x8B):
_construct = Struct('tpdu'/HexAdapter(GreedyBytes)) _construct = Struct('tpdu'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.15 # TS 102 223 Section 8.15
class TextString(COMPR_TLV_IE, tag=0x0d): class TextString(COMPR_TLV_IE, tag=0x0d):
_construct = Struct('dcs'/Int8ub, _construct = Struct('dcs'/Int8ub,
'text_string'/HexAdapter(GreedyBytes)) 'text_string'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.16 # TS 102 223 Section 8.16
class Tone(COMPR_TLV_IE, tag=0x0e): class Tone(COMPR_TLV_IE, tag=0x0e):
_construct = Struct('tone'/Int8ub) _construct = Struct('tone'/Int8ub)
# TS 31 111 Section 8.17 # TS 31 111 Section 8.17
class USSDString(COMPR_TLV_IE, tag=0x0a): class USSDString(COMPR_TLV_IE, tag=0x0a):
_construct = Struct('dcs'/Int8ub, _construct = Struct('dcs'/Int8ub,
'ussd_string'/HexAdapter(GreedyBytes)) 'ussd_string'/HexAdapter(GreedyBytes))
# TS 101 220 Table 7.17 # TS 101 220 Table 7.17
class ProactiveCommand(BER_TLV_IE, tag=0xD0): class ProactiveCommand(BER_TLV_IE, tag=0xD0):
pass pass
# TS 101 220 Table 7.17 + 31.111 7.1.1.2 # TS 101 220 Table 7.17 + 31.111 7.1.1.2
class SMSPPDownload(BER_TLV_IE, tag=0xD1, class SMSPPDownload(BER_TLV_IE, tag=0xD1,
nested=[DeviceIdentities, Address, SMS_TPDU]): nested=[DeviceIdentities, Address, SMS_TPDU]):
pass pass
# TS 101 220 Table 7.17 + 31.111 7.1.1.3 # TS 101 220 Table 7.17 + 31.111 7.1.1.3
class SMSCBDownload(BER_TLV_IE, tag=0xD2, class SMSCBDownload(BER_TLV_IE, tag=0xD2,
nested=[DeviceIdentities, CBSPage]): nested=[DeviceIdentities, CBSPage]):
pass pass
class USSDDownload(BER_TLV_IE, tag=0xD9, class USSDDownload(BER_TLV_IE, tag=0xD9,
nested=[DeviceIdentities, USSDString]): nested=[DeviceIdentities, USSDString]):
pass pass

View File

@@ -26,6 +26,7 @@ from pySim.construct import LV
from pySim.utils import rpad, b2h, h2b, sw_match, bertlv_encode_len, Hexstr, h2i, str_sanitize from pySim.utils import rpad, b2h, h2b, sw_match, bertlv_encode_len, Hexstr, h2i, str_sanitize
from pySim.exceptions import SwMatchError from pySim.exceptions import SwMatchError
class SimCardCommands(object): class SimCardCommands(object):
def __init__(self, transport): def __init__(self, transport):
self._tp = transport self._tp = transport
@@ -37,13 +38,15 @@ class SimCardCommands(object):
# see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF, # see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF,
# DF or ADF # DF or ADF
from pytlv.TLV import TLV from pytlv.TLV import TLV
tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b', '8c', '80', 'ab', 'c6', '81', '88']) tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b',
'8c', '80', 'ab', 'c6', '81', '88'])
# pytlv is case sensitive! # pytlv is case sensitive!
fcp = fcp.lower() fcp = fcp.lower()
if fcp[0:2] != '62': if fcp[0:2] != '62':
raise ValueError('Tag of the FCP template does not match, expected 62 but got %s'%fcp[0:2]) raise ValueError(
'Tag of the FCP template does not match, expected 62 but got %s' % fcp[0:2])
# Unfortunately the spec is not very clear if the FCP length is # Unfortunately the spec is not very clear if the FCP length is
# coded as one or two byte vale, so we have to try it out by # coded as one or two byte vale, so we have to try it out by
@@ -100,7 +103,8 @@ class SimCardCommands(object):
if type(dir_list) is not list: if type(dir_list) is not list:
dir_list = [dir_list] dir_list = [dir_list]
for i in dir_list: for i in dir_list:
data, sw = self._tp.send_apdu(self.cla_byte + "a4" + self.sel_ctrl + "02" + i) data, sw = self._tp.send_apdu(
self.cla_byte + "a4" + self.sel_ctrl + "02" + i)
rv.append((data, sw)) rv.append((data, sw))
if sw != '9000': if sw != '9000':
return rv return rv
@@ -162,11 +166,13 @@ class SimCardCommands(object):
chunk_offset = 0 chunk_offset = 0
while chunk_offset < length: while chunk_offset < length:
chunk_len = min(255, length-chunk_offset) chunk_len = min(255, length-chunk_offset)
pdu = self.cla_byte + 'b0%04x%02x' % (offset + chunk_offset, chunk_len) pdu = self.cla_byte + \
'b0%04x%02x' % (offset + chunk_offset, chunk_len)
try: try:
data, sw = self._tp.send_apdu_checksw(pdu) data, sw = self._tp.send_apdu_checksw(pdu)
except Exception as e: except Exception as e:
raise ValueError('%s, failed to read (offset %d)' % (str_sanitize(str(e)), offset)) raise ValueError('%s, failed to read (offset %d)' %
(str_sanitize(str(e)), offset))
total_data += data total_data += data
chunk_offset += chunk_len chunk_offset += chunk_len
return total_data, sw return total_data, sw
@@ -194,11 +200,13 @@ class SimCardCommands(object):
while chunk_offset < data_length: while chunk_offset < data_length:
chunk_len = min(255, data_length - chunk_offset) chunk_len = min(255, data_length - chunk_offset)
# chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2 # chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2
pdu = self.cla_byte + 'd6%04x%02x' % (offset + chunk_offset, chunk_len) + data[chunk_offset*2 : (chunk_offset+chunk_len)*2] pdu = self.cla_byte + \
'd6%04x%02x' % (offset + chunk_offset, chunk_len) + \
data[chunk_offset*2: (chunk_offset+chunk_len)*2]
try: try:
chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu) chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu)
except Exception as e: except Exception as e:
raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' % \ raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' %
(str_sanitize(str(e)), chunk_offset, chunk_len)) (str_sanitize(str(e)), chunk_offset, chunk_len))
total_data += data total_data += data
chunk_offset += chunk_len chunk_offset += chunk_len
@@ -216,7 +224,8 @@ class SimCardCommands(object):
""" """
res = self.read_binary(ef, len(data) // 2, offset) res = self.read_binary(ef, len(data) // 2, offset)
if res[0].lower() != data.lower(): if res[0].lower() != data.lower():
raise ValueError('Binary verification failed (expected %s, got %s)' % (data.lower(), res[0].lower())) raise ValueError('Binary verification failed (expected %s, got %s)' % (
data.lower(), res[0].lower()))
def read_record(self, ef, rec_no: int): def read_record(self, ef, rec_no: int):
"""Execute READ RECORD. """Execute READ RECORD.
@@ -253,7 +262,8 @@ class SimCardCommands(object):
# exceed we throw an exception. # exceed we throw an exception.
rec_length = self.__record_len(res) rec_length = self.__record_len(res)
if (len(data) // 2 > rec_length): if (len(data) // 2 > rec_length):
raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (rec_length, len(data) // 2)) raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (
rec_length, len(data) // 2))
elif (len(data) // 2 < rec_length): elif (len(data) // 2 < rec_length):
data = rpad(data, rec_length * 2) data = rpad(data, rec_length * 2)
@@ -280,7 +290,8 @@ class SimCardCommands(object):
""" """
res = self.read_record(ef, rec_no) res = self.read_record(ef, rec_no)
if res[0].lower() != data.lower(): if res[0].lower() != data.lower():
raise ValueError('Record verification failed (expected %s, got %s)' % (data.lower(), res[0].lower())) raise ValueError('Record verification failed (expected %s, got %s)' % (
data.lower(), res[0].lower()))
def record_size(self, ef): def record_size(self, ef):
"""Determine the record size of given file. """Determine the record size of given file.
@@ -400,7 +411,8 @@ class SimCardCommands(object):
# 3GPP TS 31.102 Section 7.1.2.1 # 3GPP TS 31.102 Section 7.1.2.1
AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV)) AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV))
AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV) AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV)
AuthResp3GSuccess = Struct(Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV)) AuthResp3GSuccess = Struct(
Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV))
AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess) AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess)
# build parameters # build parameters
cmd_data = {'rand': rand, 'autn': autn} cmd_data = {'rand': rand, 'autn': autn}
@@ -408,7 +420,8 @@ class SimCardCommands(object):
p2 = '81' p2 = '81'
elif context == 'gsm': elif context == 'gsm':
p2 = '80' p2 = '80'
(data, sw) = self._tp.send_apdu_constr_checksw(self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G) (data, sw) = self._tp.send_apdu_constr_checksw(
self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G)
if 'auts' in data: if 'auts' in data:
ret = {'synchronisation_failure': data} ret = {'synchronisation_failure': data}
else: else:
@@ -464,7 +477,8 @@ class SimCardCommands(object):
code : chv code as hex string code : chv code as hex string
""" """
fc = rpad(b2h(code), 16) fc = rpad(b2h(code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)
self._chv_process_sw('verify', chv_no, code, sw) self._chv_process_sw('verify', chv_no, code, sw)
return (data, sw) return (data, sw)
@@ -477,7 +491,8 @@ class SimCardCommands(object):
pin_code : new chv code as hex string pin_code : new chv code as hex string
""" """
fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16) fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc)
self._chv_process_sw('unblock', chv_no, pin_code, sw) self._chv_process_sw('unblock', chv_no, pin_code, sw)
return (data, sw) return (data, sw)
@@ -490,7 +505,8 @@ class SimCardCommands(object):
new_pin_code : new chv code as hex string new_pin_code : new chv code as hex string
""" """
fc = rpad(b2h(pin_code), 16) + rpad(b2h(new_pin_code), 16) fc = rpad(b2h(pin_code), 16) + rpad(b2h(new_pin_code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc)
self._chv_process_sw('change', chv_no, pin_code, sw) self._chv_process_sw('change', chv_no, pin_code, sw)
return (data, sw) return (data, sw)
@@ -503,7 +519,8 @@ class SimCardCommands(object):
new_pin_code : new chv code as hex string new_pin_code : new chv code as hex string
""" """
fc = rpad(b2h(pin_code), 16) fc = rpad(b2h(pin_code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc)
self._chv_process_sw('disable', chv_no, pin_code, sw) self._chv_process_sw('disable', chv_no, pin_code, sw)
return (data, sw) return (data, sw)
@@ -515,7 +532,8 @@ class SimCardCommands(object):
pin_code : chv code as hex string pin_code : chv code as hex string
""" """
fc = rpad(b2h(pin_code), 16) fc = rpad(b2h(pin_code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc)
self._chv_process_sw('enable', chv_no, pin_code, sw) self._chv_process_sw('enable', chv_no, pin_code, sw)
return (data, sw) return (data, sw)
@@ -556,6 +574,7 @@ class SimCardCommands(object):
return '01%02x' % (secs // 60) return '01%02x' % (secs // 60)
else: else:
return '00%02x' % secs return '00%02x' % secs
def decode_duration(enc: Hexstr) -> int: def decode_duration(enc: Hexstr) -> int:
time_unit = enc[:2] time_unit = enc[:2]
length = h2i(enc[2:4]) length = h2i(enc[2:4])
@@ -573,7 +592,8 @@ class SimCardCommands(object):
raise ValueError('Time unit must be 0x00..0x04') raise ValueError('Time unit must be 0x00..0x04')
min_dur_enc = encode_duration(min_len_secs) min_dur_enc = encode_duration(min_len_secs)
max_dur_enc = encode_duration(max_len_secs) max_dur_enc = encode_duration(max_len_secs)
data, sw = self._tp.send_apdu_checksw('8076000004' + min_dur_enc + max_dur_enc) data, sw = self._tp.send_apdu_checksw(
'8076000004' + min_dur_enc + max_dur_enc)
negotiated_duration_secs = decode_duration(data[:4]) negotiated_duration_secs = decode_duration(data[:4])
resume_token = data[4:] resume_token = data[4:]
return (negotiated_duration_secs, resume_token, sw) return (negotiated_duration_secs, resume_token, sw)

View File

@@ -1,3 +1,5 @@
from construct.lib.containers import Container, ListContainer
from construct.core import EnumIntegerString
import typing import typing
from construct import * from construct import *
from pySim.utils import b2h, h2b, swap_nibbles from pySim.utils import b2h, h2b, swap_nibbles
@@ -23,18 +25,24 @@ import gsm0338
class HexAdapter(Adapter): class HexAdapter(Adapter):
"""convert a bytes() type to a string of hex nibbles.""" """convert a bytes() type to a string of hex nibbles."""
def _decode(self, obj, context, path): def _decode(self, obj, context, path):
return b2h(obj) return b2h(obj)
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
return h2b(obj) return h2b(obj)
class BcdAdapter(Adapter): class BcdAdapter(Adapter):
"""convert a bytes() type to a string of BCD nibbles.""" """convert a bytes() type to a string of BCD nibbles."""
def _decode(self, obj, context, path): def _decode(self, obj, context, path):
return swap_nibbles(b2h(obj)) return swap_nibbles(b2h(obj))
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
return h2b(swap_nibbles(obj)) return h2b(swap_nibbles(obj))
class Rpad(Adapter): class Rpad(Adapter):
""" """
Encoder appends padding bytes (b'\\xff') up to target size. Encoder appends padding bytes (b'\\xff') up to target size.
@@ -54,9 +62,11 @@ class Rpad(Adapter):
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
if len(obj) > self.sizeof(): if len(obj) > self.sizeof():
raise SizeofError("Input ({}) exceeds target size ({})".format(len(obj), self.sizeof())) raise SizeofError("Input ({}) exceeds target size ({})".format(
len(obj), self.sizeof()))
return obj + self.pattern * (self.sizeof() - len(obj)) return obj + self.pattern * (self.sizeof() - len(obj))
class GsmStringAdapter(Adapter): class GsmStringAdapter(Adapter):
"""Convert GSM 03.38 encoded bytes to a string.""" """Convert GSM 03.38 encoded bytes to a string."""
@@ -71,6 +81,7 @@ class GsmStringAdapter(Adapter):
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
return obj.encode(self.codec, self.err) return obj.encode(self.codec, self.err)
def filter_dict(d, exclude_prefix='_'): def filter_dict(d, exclude_prefix='_'):
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain.""" """filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
if not isinstance(d, dict): if not isinstance(d, dict):
@@ -85,8 +96,6 @@ def filter_dict(d, exclude_prefix='_'):
res[key] = value res[key] = value
return res return res
from construct.lib.containers import Container, ListContainer
from construct.core import EnumIntegerString
def normalize_construct(c): def normalize_construct(c):
"""Convert a construct specific type to a related base type, mostly useful """Convert a construct specific type to a related base type, mostly useful
@@ -106,6 +115,7 @@ def normalize_construct(c):
r = c r = c
return r return r
def parse_construct(c, raw_bin_data: bytes, length: typing.Optional[int] = None, exclude_prefix: str = '_'): def parse_construct(c, raw_bin_data: bytes, length: typing.Optional[int] = None, exclude_prefix: str = '_'):
"""Helper function to wrap around normalize_construct() and filter_dict().""" """Helper function to wrap around normalize_construct() and filter_dict()."""
if not length: if not length:
@@ -113,6 +123,7 @@ def parse_construct(c, raw_bin_data:bytes, length:typing.Optional[int]=None, exc
parsed = c.parse(raw_bin_data, total_len=length) parsed = c.parse(raw_bin_data, total_len=length)
return normalize_construct(parsed) return normalize_construct(parsed)
# here we collect some shared / common definitions of data types # here we collect some shared / common definitions of data types
LV = Prefixed(Int8ub, HexAdapter(GreedyBytes)) LV = Prefixed(Int8ub, HexAdapter(GreedyBytes))
@@ -129,6 +140,7 @@ ByteRFU = Default(Byte, __RFU_VALUE)
# Field that packs all remaining Reserved for Future Use (RFU) bytes # Field that packs all remaining Reserved for Future Use (RFU) bytes
GreedyBytesRFU = Default(GreedyBytes, b'') GreedyBytesRFU = Default(GreedyBytes, b'')
def BitsRFU(n=1): def BitsRFU(n=1):
''' '''
Field that packs Reserved for Future Use (RFU) bit(s) Field that packs Reserved for Future Use (RFU) bit(s)
@@ -143,6 +155,7 @@ def BitsRFU(n=1):
''' '''
return Default(BitsInteger(n), __RFU_VALUE) return Default(BitsInteger(n), __RFU_VALUE)
def BytesRFU(n=1): def BytesRFU(n=1):
''' '''
Field that packs Reserved for Future Use (RFU) byte(s) Field that packs Reserved for Future Use (RFU) byte(s)
@@ -157,6 +170,7 @@ def BytesRFU(n=1):
''' '''
return Default(Bytes(n), __RFU_VALUE) return Default(Bytes(n), __RFU_VALUE)
def GsmString(n): def GsmString(n):
''' '''
GSM 03.38 encoded byte string of fixed length n. GSM 03.38 encoded byte string of fixed length n.

View File

@@ -21,21 +21,26 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
class NoCardError(Exception): class NoCardError(Exception):
"""No card was found in the reader.""" """No card was found in the reader."""
pass pass
class ProtocolError(Exception): class ProtocolError(Exception):
"""Some kind of protocol level error interfacing with the card.""" """Some kind of protocol level error interfacing with the card."""
pass pass
class ReaderError(Exception): class ReaderError(Exception):
"""Some kind of general error with the card reader.""" """Some kind of general error with the card reader."""
pass pass
class SwMatchError(Exception): class SwMatchError(Exception):
"""Raised when an operation specifies an expected SW but the actual SW from """Raised when an operation specifies an expected SW but the actual SW from
the card doesn't match.""" the card doesn't match."""
def __init__(self, sw_actual: str, sw_expected: str, rs=None): def __init__(self, sw_actual: str, sw_expected: str, rs=None):
""" """
Args: Args:
@@ -46,6 +51,7 @@ class SwMatchError(Exception):
self.sw_actual = sw_actual self.sw_actual = sw_actual
self.sw_expected = sw_expected self.sw_expected = sw_expected
self.rs = rs self.rs = rs
def __str__(self): def __str__(self):
if self.rs: if self.rs:
r = self.rs.interpret_sw(self.sw_actual) r = self.rs.interpret_sw(self.sw_actual)

View File

@@ -44,6 +44,7 @@ from pySim.exceptions import *
from pySim.jsonpath import js_path_find, js_path_modify from pySim.jsonpath import js_path_find, js_path_modify
from pySim.commands import SimCardCommands from pySim.commands import SimCardCommands
class CardFile(object): class CardFile(object):
"""Base class for all objects in the smart card filesystem. """Base class for all objects in the smart card filesystem.
Serve as a common ancestor to all other file types; rarely used directly. Serve as a common ancestor to all other file types; rarely used directly.
@@ -206,6 +207,7 @@ class CardFile(object):
return self.parent.get_profile() return self.parent.get_profile()
return None return None
class CardDF(CardFile): class CardDF(CardFile):
"""DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories.""" """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
@@ -242,13 +244,16 @@ class CardDF(CardFile):
if child.fid in self.children: if child.fid in self.children:
if ignore_existing: if ignore_existing:
return return
raise ValueError("File with given fid %s already exists in %s" % (child.fid, self)) raise ValueError(
"File with given fid %s already exists in %s" % (child.fid, self))
if self.lookup_file_by_sfid(child.sfid): if self.lookup_file_by_sfid(child.sfid):
raise ValueError("File with given sfid %s already exists in %s" % (child.sfid, self)) raise ValueError(
"File with given sfid %s already exists in %s" % (child.sfid, self))
if self.lookup_file_by_name(child.name): if self.lookup_file_by_name(child.name):
if ignore_existing: if ignore_existing:
return return
raise ValueError("File with given name %s already exists in %s" % (child.name, self)) raise ValueError(
"File with given name %s already exists in %s" % (child.name, self))
self.children[child.fid] = child self.children[child.fid] = child
child.parent = self child.parent = self
@@ -307,6 +312,7 @@ class CardDF(CardFile):
class CardMF(CardDF): class CardMF(CardDF):
"""MF (Master File) in the smart card filesystem""" """MF (Master File) in the smart card filesystem"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
# can be overridden; use setdefault # can be overridden; use setdefault
kwargs.setdefault('fid', '3f00') kwargs.setdefault('fid', '3f00')
@@ -353,7 +359,8 @@ class CardMF(CardDF):
if flags == [] or 'AIDS' in flags: if flags == [] or 'AIDS' in flags:
sels.update({x.aid: x for x in self.applications.values()}) sels.update({x.aid: x for x in self.applications.values()})
if flags == [] or 'ANAMES' in flags: if flags == [] or 'ANAMES' in flags:
sels.update({x.name: x for x in self.applications.values() if x.name}) sels.update(
{x.name: x for x in self.applications.values() if x.name})
return sels return sels
def decode_select_response(self, data_hex: str) -> object: def decode_select_response(self, data_hex: str) -> object:
@@ -372,8 +379,10 @@ class CardMF(CardDF):
else: else:
return data_hex return data_hex
class CardADF(CardDF): class CardADF(CardDF):
"""ADF (Application Dedicated File) in the smart card filesystem""" """ADF (Application Dedicated File) in the smart card filesystem"""
def __init__(self, aid: str, **kwargs): def __init__(self, aid: str, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
# reference to CardApplication may be set from CardApplication constructor # reference to CardApplication may be set from CardApplication constructor
@@ -395,6 +404,7 @@ class CardADF(CardDF):
class CardEF(CardFile): class CardEF(CardFile):
"""EF (Entry File) in the smart card filesystem""" """EF (Entry File) in the smart card filesystem"""
def __init__(self, *, fid, **kwargs): def __init__(self, *, fid, **kwargs):
kwargs['fid'] = fid kwargs['fid'] = fid
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -414,7 +424,8 @@ class CardEF(CardFile):
""" """
# global selectable names + those of the parent DF # global selectable names + those of the parent DF
sels = super().get_selectables(flags) sels = super().get_selectables(flags)
sels.update({x.name:x for x in self.parent.children.values() if x != self}) sels.update(
{x.name: x for x in self.parent.children.values() if x != self})
return sels return sels
@@ -427,12 +438,16 @@ class TransparentEF(CardEF):
@with_default_category('Transparent EF Commands') @with_default_category('Transparent EF Commands')
class ShellCommands(CommandSet): class ShellCommands(CommandSet):
"""Shell commands specific for transparent EFs.""" """Shell commands specific for transparent EFs."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
read_bin_parser = argparse.ArgumentParser() read_bin_parser = argparse.ArgumentParser()
read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read') read_bin_parser.add_argument(
read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read') '--offset', type=int, default=0, help='Byte offset for start of read')
read_bin_parser.add_argument(
'--length', type=int, help='Number of bytes to read')
@cmd2.with_argparser(read_bin_parser) @cmd2.with_argparser(read_bin_parser)
def do_read_binary(self, opts): def do_read_binary(self, opts):
"""Read binary data from a transparent EF""" """Read binary data from a transparent EF"""
@@ -442,6 +457,7 @@ class TransparentEF(CardEF):
read_bin_dec_parser = argparse.ArgumentParser() read_bin_dec_parser = argparse.ArgumentParser()
read_bin_dec_parser.add_argument('--oneline', action='store_true', read_bin_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line') help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_bin_dec_parser) @cmd2.with_argparser(read_bin_dec_parser)
def do_read_binary_decoded(self, opts): def do_read_binary_decoded(self, opts):
"""Read + decode data from a transparent EF""" """Read + decode data from a transparent EF"""
@@ -449,8 +465,11 @@ class TransparentEF(CardEF):
self._cmd.poutput_json(data, opts.oneline) self._cmd.poutput_json(data, opts.oneline)
upd_bin_parser = argparse.ArgumentParser() upd_bin_parser = argparse.ArgumentParser()
upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read') upd_bin_parser.add_argument(
upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write') '--offset', type=int, default=0, help='Byte offset for start of read')
upd_bin_parser.add_argument(
'data', help='Data bytes (hex format) to write')
@cmd2.with_argparser(upd_bin_parser) @cmd2.with_argparser(upd_bin_parser)
def do_update_binary(self, opts): def do_update_binary(self, opts):
"""Update (Write) data of a transparent EF""" """Update (Write) data of a transparent EF"""
@@ -459,15 +478,18 @@ class TransparentEF(CardEF):
self._cmd.poutput(data) self._cmd.poutput(data)
upd_bin_dec_parser = argparse.ArgumentParser() upd_bin_dec_parser = argparse.ArgumentParser()
upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write') upd_bin_dec_parser.add_argument(
'data', help='Abstract data (JSON format) to write')
upd_bin_dec_parser.add_argument('--json-path', type=str, upd_bin_dec_parser.add_argument('--json-path', type=str,
help='JSON path to modify specific element of file only') help='JSON path to modify specific element of file only')
@cmd2.with_argparser(upd_bin_dec_parser) @cmd2.with_argparser(upd_bin_dec_parser)
def do_update_binary_decoded(self, opts): def do_update_binary_decoded(self, opts):
"""Encode + Update (Write) data of a transparent EF""" """Encode + Update (Write) data of a transparent EF"""
if opts.json_path: if opts.json_path:
(data_json, sw) = self._cmd.rs.read_binary_dec() (data_json, sw) = self._cmd.rs.read_binary_dec()
js_path_modify(data_json, opts.json_path, json.loads(opts.data)) js_path_modify(data_json, opts.json_path,
json.loads(opts.data))
else: else:
data_json = json.loads(opts.data) data_json = json.loads(opts.data)
(data, sw) = self._cmd.rs.update_binary_dec(data_json) (data, sw) = self._cmd.rs.update_binary_dec(data_json)
@@ -493,7 +515,6 @@ class TransparentEF(CardEF):
if data: if data:
self._cmd.poutput_json(data) self._cmd.poutput_json(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None, def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
size={1, None}): size={1, None}):
""" """
@@ -588,7 +609,8 @@ class TransparentEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() return t.to_tlv()
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def encode_hex(self, abstract_data: dict) -> str: def encode_hex(self, abstract_data: dict) -> str:
"""Encode abstract representation into raw (hex string) data. """Encode abstract representation into raw (hex string) data.
@@ -615,7 +637,8 @@ class TransparentEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) return b2h(t.to_tlv())
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
class LinFixedEF(CardEF): class LinFixedEF(CardEF):
@@ -627,12 +650,16 @@ class LinFixedEF(CardEF):
@with_default_category('Linear Fixed EF Commands') @with_default_category('Linear Fixed EF Commands')
class ShellCommands(CommandSet): class ShellCommands(CommandSet):
"""Shell commands specific for Linear Fixed EFs.""" """Shell commands specific for Linear Fixed EFs."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
read_rec_parser = argparse.ArgumentParser() read_rec_parser = argparse.ArgumentParser()
read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read') read_rec_parser.add_argument(
read_rec_parser.add_argument('--count', type=int, default=1, help='Number of records to be read, beginning at record_nr') 'record_nr', type=int, help='Number of record to be read')
read_rec_parser.add_argument(
'--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
@cmd2.with_argparser(read_rec_parser) @cmd2.with_argparser(read_rec_parser)
def do_read_record(self, opts): def do_read_record(self, opts):
"""Read one or multiple records from a record-oriented EF""" """Read one or multiple records from a record-oriented EF"""
@@ -646,9 +673,11 @@ class LinFixedEF(CardEF):
self._cmd.poutput("%03d %s" % (recnr, recstr)) self._cmd.poutput("%03d %s" % (recnr, recstr))
read_rec_dec_parser = argparse.ArgumentParser() read_rec_dec_parser = argparse.ArgumentParser()
read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read') read_rec_dec_parser.add_argument(
'record_nr', type=int, help='Number of record to be read')
read_rec_dec_parser.add_argument('--oneline', action='store_true', read_rec_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line') help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_rec_dec_parser) @cmd2.with_argparser(read_rec_dec_parser)
def do_read_record_decoded(self, opts): def do_read_record_decoded(self, opts):
"""Read + decode a record from a record-oriented EF""" """Read + decode a record from a record-oriented EF"""
@@ -656,6 +685,7 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data, opts.oneline) self._cmd.poutput_json(data, opts.oneline)
read_recs_parser = argparse.ArgumentParser() read_recs_parser = argparse.ArgumentParser()
@cmd2.with_argparser(read_recs_parser) @cmd2.with_argparser(read_recs_parser)
def do_read_records(self, opts): def do_read_records(self, opts):
"""Read all records from a record-oriented EF""" """Read all records from a record-oriented EF"""
@@ -671,6 +701,7 @@ class LinFixedEF(CardEF):
read_recs_dec_parser = argparse.ArgumentParser() read_recs_dec_parser = argparse.ArgumentParser()
read_recs_dec_parser.add_argument('--oneline', action='store_true', read_recs_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line') help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_recs_dec_parser) @cmd2.with_argparser(read_recs_dec_parser)
def do_read_records_decoded(self, opts): def do_read_records_decoded(self, opts):
"""Read + decode all records from a record-oriented EF""" """Read + decode all records from a record-oriented EF"""
@@ -683,8 +714,11 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data_list, opts.oneline) self._cmd.poutput_json(data_list, opts.oneline)
upd_rec_parser = argparse.ArgumentParser() upd_rec_parser = argparse.ArgumentParser()
upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read') upd_rec_parser.add_argument(
upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write') 'record_nr', type=int, help='Number of record to be read')
upd_rec_parser.add_argument(
'data', help='Data bytes (hex format) to write')
@cmd2.with_argparser(upd_rec_parser) @cmd2.with_argparser(upd_rec_parser)
def do_update_record(self, opts): def do_update_record(self, opts):
"""Update (write) data to a record-oriented EF""" """Update (write) data to a record-oriented EF"""
@@ -693,24 +727,31 @@ class LinFixedEF(CardEF):
self._cmd.poutput(data) self._cmd.poutput(data)
upd_rec_dec_parser = argparse.ArgumentParser() upd_rec_dec_parser = argparse.ArgumentParser()
upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read') upd_rec_dec_parser.add_argument(
upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write') 'record_nr', type=int, help='Number of record to be read')
upd_rec_dec_parser.add_argument(
'data', help='Abstract data (JSON format) to write')
upd_rec_dec_parser.add_argument('--json-path', type=str, upd_rec_dec_parser.add_argument('--json-path', type=str,
help='JSON path to modify specific element of record only') help='JSON path to modify specific element of record only')
@cmd2.with_argparser(upd_rec_dec_parser) @cmd2.with_argparser(upd_rec_dec_parser)
def do_update_record_decoded(self, opts): def do_update_record_decoded(self, opts):
"""Encode + Update (write) data to a record-oriented EF""" """Encode + Update (write) data to a record-oriented EF"""
if opts.json_path: if opts.json_path:
(data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr) (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
js_path_modify(data_json, opts.json_path, json.loads(opts.data)) js_path_modify(data_json, opts.json_path,
json.loads(opts.data))
else: else:
data_json = json.loads(opts.data) data_json = json.loads(opts.data)
(data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json) (data, sw) = self._cmd.rs.update_record_dec(
opts.record_nr, data_json)
if data: if data:
self._cmd.poutput(data) self._cmd.poutput(data)
edit_rec_dec_parser = argparse.ArgumentParser() edit_rec_dec_parser = argparse.ArgumentParser()
edit_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be edited') edit_rec_dec_parser.add_argument(
'record_nr', type=int, help='Number of record to be edited')
@cmd2.with_argparser(edit_rec_dec_parser) @cmd2.with_argparser(edit_rec_dec_parser)
def do_edit_record_decoded(self, opts): def do_edit_record_decoded(self, opts):
"""Edit the JSON representation of one record in an editor.""" """Edit the JSON representation of one record in an editor."""
@@ -727,11 +768,11 @@ class LinFixedEF(CardEF):
if edited_json == orig_json: if edited_json == orig_json:
self._cmd.poutput("Data not modified, skipping write") self._cmd.poutput("Data not modified, skipping write")
else: else:
(data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, edited_json) (data, sw) = self._cmd.rs.update_record_dec(
opts.record_nr, edited_json)
if data: if data:
self._cmd.poutput_json(data) self._cmd.poutput_json(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
parent: Optional[CardDF] = None, rec_len={1, None}): parent: Optional[CardDF] = None, rec_len={1, None}):
""" """
@@ -828,7 +869,8 @@ class LinFixedEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) return b2h(t.to_tlv())
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def encode_record_bin(self, abstract_data: dict) -> bytearray: def encode_record_bin(self, abstract_data: dict) -> bytearray:
"""Encode abstract representation into raw (binary) data. """Encode abstract representation into raw (binary) data.
@@ -854,14 +896,19 @@ class LinFixedEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() return t.to_tlv()
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
class CyclicEF(LinFixedEF): class CyclicEF(LinFixedEF):
"""Cyclic EF (Entry File) in the smart card filesystem""" """Cyclic EF (Entry File) in the smart card filesystem"""
# we don't really have any special support for those; just recycling LinFixedEF here # we don't really have any special support for those; just recycling LinFixedEF here
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None, def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
rec_len={1, None}): rec_len={1, None}):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len) super().__init__(fid=fid, sfid=sfid, name=name,
desc=desc, parent=parent, rec_len=rec_len)
class TransRecEF(TransparentEF): class TransRecEF(TransparentEF):
"""Transparent EF (Entry File) containing fixed-size records. """Transparent EF (Entry File) containing fixed-size records.
@@ -872,6 +919,7 @@ class TransRecEF(TransparentEF):
We add a special class for those, so the user only has to provide encoder/decoder functions We add a special class for those, so the user only has to provide encoder/decoder functions
for a record, while this class takes care of split / merge of records. for a record, while this class takes care of split / merge of records.
""" """
def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None, def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
parent: Optional[CardDF] = None, size={1, None}): parent: Optional[CardDF] = None, size={1, None}):
""" """
@@ -965,7 +1013,8 @@ class TransRecEF(TransparentEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) return b2h(t.to_tlv())
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def encode_record_bin(self, abstract_data: dict) -> bytearray: def encode_record_bin(self, abstract_data: dict) -> bytearray:
"""Encode abstract representation into raw (binary) data. """Encode abstract representation into raw (binary) data.
@@ -991,10 +1040,12 @@ class TransRecEF(TransparentEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() return t.to_tlv()
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def _decode_bin(self, raw_bin_data: bytearray): def _decode_bin(self, raw_bin_data: bytearray):
chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)] chunks = [raw_bin_data[i:i+self.rec_len]
for i in range(0, len(raw_bin_data), self.rec_len)]
return [self.decode_record_bin(x) for x in chunks] return [self.decode_record_bin(x) for x in chunks]
def _encode_bin(self, abstract_data) -> bytes: def _encode_bin(self, abstract_data) -> bytes:
@@ -1014,11 +1065,14 @@ class BerTlvEF(CardEF):
@with_default_category('BER-TLV EF Commands') @with_default_category('BER-TLV EF Commands')
class ShellCommands(CommandSet): class ShellCommands(CommandSet):
"""Shell commands specific for BER-TLV EFs.""" """Shell commands specific for BER-TLV EFs."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
retrieve_data_parser = argparse.ArgumentParser() retrieve_data_parser = argparse.ArgumentParser()
retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve') retrieve_data_parser.add_argument(
'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
@cmd2.with_argparser(retrieve_data_parser) @cmd2.with_argparser(retrieve_data_parser)
def do_retrieve_data(self, opts): def do_retrieve_data(self, opts):
"""Retrieve (Read) data from a BER-TLV EF""" """Retrieve (Read) data from a BER-TLV EF"""
@@ -1031,8 +1085,11 @@ class BerTlvEF(CardEF):
self._cmd.poutput(tags) self._cmd.poutput(tags)
set_data_parser = argparse.ArgumentParser() set_data_parser = argparse.ArgumentParser()
set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set') set_data_parser.add_argument(
set_data_parser.add_argument('data', help='Data bytes (hex format) to write') 'tag', type=auto_int, help='BER-TLV Tag of value to set')
set_data_parser.add_argument(
'data', help='Data bytes (hex format) to write')
@cmd2.with_argparser(set_data_parser) @cmd2.with_argparser(set_data_parser)
def do_set_data(self, opts): def do_set_data(self, opts):
"""Set (Write) data for a given tag in a BER-TLV EF""" """Set (Write) data for a given tag in a BER-TLV EF"""
@@ -1041,7 +1098,9 @@ class BerTlvEF(CardEF):
self._cmd.poutput(data) self._cmd.poutput(data)
del_data_parser = argparse.ArgumentParser() del_data_parser = argparse.ArgumentParser()
del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set') del_data_parser.add_argument(
'tag', type=auto_int, help='BER-TLV Tag of value to set')
@cmd2.with_argparser(del_data_parser) @cmd2.with_argparser(del_data_parser)
def do_delete_data(self, opts): def do_delete_data(self, opts):
"""Delete data for a given tag in a BER-TLV EF""" """Delete data for a given tag in a BER-TLV EF"""
@@ -1049,7 +1108,6 @@ class BerTlvEF(CardEF):
if data: if data:
self._cmd.poutput(data) self._cmd.poutput(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None, def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
size={1, None}): size={1, None}):
""" """
@@ -1069,6 +1127,7 @@ class BerTlvEF(CardEF):
class RuntimeState(object): class RuntimeState(object):
"""Represent the runtime state of a session with a card.""" """Represent the runtime state of a session with a card."""
def __init__(self, card, profile: 'CardProfile'): def __init__(self, card, profile: 'CardProfile'):
""" """
Args: Args:
@@ -1082,7 +1141,8 @@ class RuntimeState(object):
# make sure the class and selection control bytes, which are specified # make sure the class and selection control bytes, which are specified
# by the card profile are used # by the card profile are used
self.card.set_apdu_parameter(cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl) self.card.set_apdu_parameter(
cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
# add application ADFs + MF-files from profile # add application ADFs + MF-files from profile
apps = self._match_applications() apps = self._match_applications()
@@ -1195,7 +1255,8 @@ class RuntimeState(object):
"""Blindly try to select a file and automatically add a matching file """Blindly try to select a file and automatically add a matching file
object if the file actually exists.""" object if the file actually exists."""
if not is_hex(fid, 4, 4): if not is_hex(fid, 4, 4):
raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid) raise ValueError(
"Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
try: try:
(data, sw) = self.card._scc.select_file(fid) (data, sw) = self.card._scc.select_file(fid)
@@ -1207,12 +1268,15 @@ class RuntimeState(object):
select_resp = self.selected_file.decode_select_response(data) select_resp = self.selected_file.decode_select_response(data)
if (select_resp['file_descriptor']['file_type'] == 'df'): if (select_resp['file_descriptor']['file_type'] == 'df'):
f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime") f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
desc="dedicated file, manually added at runtime")
else: else:
if (select_resp['file_descriptor']['structure'] == 'transparent'): if (select_resp['file_descriptor']['structure'] == 'transparent'):
f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime") f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
desc="elementary file, manually added at runtime")
else: else:
f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime") f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
desc="elementary file, manually added at runtime")
self.selected_file.add_files([f]) self.selected_file.add_files([f])
self.selected_file = f self.selected_file = f
@@ -1408,9 +1472,9 @@ class RuntimeState(object):
cmd_app.unregister_command_set(c) cmd_app.unregister_command_set(c)
class FileData(object): class FileData(object):
"""Represent the runtime, on-card data.""" """Represent the runtime, on-card data."""
def __init__(self, fdesc): def __init__(self, fdesc):
self.desc = fdesc self.desc = fdesc
self.fcp = None self.fcp = None
@@ -1435,9 +1499,11 @@ def interpret_sw(sw_data:dict, sw:str):
return (class_str, descr) return (class_str, descr)
return None return None
class CardApplication(object): class CardApplication(object):
"""A card application is represented by an ADF (with contained hierarchy) and optionally """A card application is represented by an ADF (with contained hierarchy) and optionally
some SW definitions.""" some SW definitions."""
def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None): def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
""" """
Args: Args:

View File

@@ -43,6 +43,7 @@ import pySim.ts_51_011
# DF.EIRENE (FFFIS for GSM-R SIM Cards) # DF.EIRENE (FFFIS for GSM-R SIM Cards)
###################################################################### ######################################################################
class FuncNTypeAdapter(Adapter): class FuncNTypeAdapter(Adapter):
def _decode(self, obj, context, path): def _decode(self, obj, context, path):
bcd = swap_nibbles(b2h(obj)) bcd = swap_nibbles(b2h(obj))
@@ -50,19 +51,24 @@ class FuncNTypeAdapter(Adapter):
return {'functional_number': bcd[:-1], return {'functional_number': bcd[:-1],
'presentation_of_only_this_fn': last_digit & 4, 'presentation_of_only_this_fn': last_digit & 4,
'permanent_fn': last_digit & 8} 'permanent_fn': last_digit & 8}
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
return 'FIXME' return 'FIXME'
class EF_FN(LinFixedEF): class EF_FN(LinFixedEF):
"""Section 7.2""" """Section 7.2"""
def __init__(self): def __init__(self):
super().__init__(fid='6ff1', sfid=None, name='EF.EN', desc='Functional numbers', rec_len={9,9}) super().__init__(fid='6ff1', sfid=None, name='EF.EN',
desc='Functional numbers', rec_len={9, 9})
self._construct = Struct('functional_number_and_type'/FuncNTypeAdapter(Bytes(8)), self._construct = Struct('functional_number_and_type'/FuncNTypeAdapter(Bytes(8)),
'list_number'/Int8ub) 'list_number'/Int8ub)
class PlConfAdapter(Adapter): class PlConfAdapter(Adapter):
"""Section 7.4.3""" """Section 7.4.3"""
def _decode(self, obj, context, path): def _decode(self, obj, context, path):
num = int(obj) & 0x7 num = int(obj) & 0x7
if num == 0: if num == 0:
@@ -77,6 +83,7 @@ class PlConfAdapter(Adapter):
return 1 return 1
elif num == 5: elif num == 5:
return 0 return 0
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
if obj == 'None': if obj == 'None':
return 0 return 0
@@ -92,8 +99,10 @@ class PlConfAdapter(Adapter):
elif obj == 0: elif obj == 0:
return 5 return 5
class PlCallAdapter(Adapter): class PlCallAdapter(Adapter):
"""Section 7.4.12""" """Section 7.4.12"""
def _decode(self, obj, context, path): def _decode(self, obj, context, path):
num = int(obj) & 0x7 num = int(obj) & 0x7
if num == 0: if num == 0:
@@ -112,6 +121,7 @@ class PlCallAdapter(Adapter):
return 'B' return 'B'
elif num == 7: elif num == 7:
return 'A' return 'A'
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
if obj == 'None': if obj == 'None':
return 0 return 0
@@ -130,10 +140,14 @@ class PlCallAdapter(Adapter):
elif obj == 'A': elif obj == 'A':
return 7 return 7
NextTableType = Enum(Byte, decision=0xf0, predefined=0xf1, num_dial_digits=0xf2, ic=0xf3, empty=0xff)
NextTableType = Enum(Byte, decision=0xf0, predefined=0xf1,
num_dial_digits=0xf2, ic=0xf3, empty=0xff)
class EF_CallconfC(TransparentEF): class EF_CallconfC(TransparentEF):
"""Section 7.3""" """Section 7.3"""
def __init__(self): def __init__(self):
super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size={24, 24}, super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size={24, 24},
desc='Call Configuration of emergency calls Configuration') desc='Call Configuration of emergency calls Configuration')
@@ -147,29 +161,39 @@ class EF_CallconfC(TransparentEF):
'shunting_emergency_gid'/Int8ub, 'shunting_emergency_gid'/Int8ub,
'imei'/BcdAdapter(Bytes(8))) 'imei'/BcdAdapter(Bytes(8)))
class EF_CallconfI(LinFixedEF): class EF_CallconfI(LinFixedEF):
"""Section 7.5""" """Section 7.5"""
def __init__(self): def __init__(self):
super().__init__(fid='6ff3', sfid=None, name='EF.CallconfI', rec_len={21, 21}, super().__init__(fid='6ff3', sfid=None, name='EF.CallconfI', rec_len={21, 21},
desc='Call Configuration of emergency calls Information') desc='Call Configuration of emergency calls Information')
self._construct = Struct('t_dur'/Int24ub, self._construct = Struct('t_dur'/Int24ub,
't_relcalc'/Int32ub, 't_relcalc'/Int32ub,
'pl_call'/PlCallAdapter(Int8ub), 'pl_call'/PlCallAdapter(Int8ub),
'cause'/FlagsEnum(Int8ub, powered_off=1, radio_link_error=2, user_command=5), 'cause' /
FlagsEnum(Int8ub, powered_off=1,
radio_link_error=2, user_command=5),
'gcr'/BcdAdapter(Bytes(4)), 'gcr'/BcdAdapter(Bytes(4)),
'fnr'/BcdAdapter(Bytes(8))) 'fnr'/BcdAdapter(Bytes(8)))
class EF_Shunting(TransparentEF): class EF_Shunting(TransparentEF):
"""Section 7.6""" """Section 7.6"""
def __init__(self): def __init__(self):
super().__init__(fid='6ff4', sfid=None, name='EF.Shunting', desc='Shunting', size={8,8}) super().__init__(fid='6ff4', sfid=None,
name='EF.Shunting', desc='Shunting', size={8, 8})
self._construct = Struct('common_gid'/Int8ub, self._construct = Struct('common_gid'/Int8ub,
'shunting_gid'/Bytes(7)) 'shunting_gid'/Bytes(7))
class EF_GsmrPLMN(LinFixedEF): class EF_GsmrPLMN(LinFixedEF):
"""Section 7.7""" """Section 7.7"""
def __init__(self): def __init__(self):
super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN', desc='GSM-R network selection', rec_len={9,9}) super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN',
desc='GSM-R network selection', rec_len={9, 9})
self._construct = Struct('plmn'/BcdAdapter(Bytes(3)), self._construct = Struct('plmn'/BcdAdapter(Bytes(3)),
'class_of_network'/BitStruct('supported'/FlagsEnum(BitsInteger(5), vbs=1, vgcs=2, emlpp=4, fn=8, eirene=16), 'class_of_network'/BitStruct('supported'/FlagsEnum(BitsInteger(5), vbs=1, vgcs=2, emlpp=4, fn=8, eirene=16),
'preference'/BitsInteger(3)), 'preference'/BitsInteger(3)),
@@ -177,34 +201,46 @@ class EF_GsmrPLMN(LinFixedEF):
'outgoing_ref_tbl'/HexAdapter(Bytes(2)), 'outgoing_ref_tbl'/HexAdapter(Bytes(2)),
'ic_table_ref'/HexAdapter(Bytes(1))) 'ic_table_ref'/HexAdapter(Bytes(1)))
class EF_IC(LinFixedEF): class EF_IC(LinFixedEF):
"""Section 7.8""" """Section 7.8"""
def __init__(self): def __init__(self):
super().__init__(fid='6f8d', sfid=None, name='EF.IC', desc='International Code', rec_len={7,7}) super().__init__(fid='6f8d', sfid=None, name='EF.IC',
desc='International Code', rec_len={7, 7})
self._construct = Struct('next_table_type'/NextTableType, self._construct = Struct('next_table_type'/NextTableType,
'id_of_next_table'/HexAdapter(Bytes(2)), 'id_of_next_table'/HexAdapter(Bytes(2)),
'ic_decision_value'/BcdAdapter(Bytes(2)), 'ic_decision_value'/BcdAdapter(Bytes(2)),
'network_string_table_index'/Int8ub) 'network_string_table_index'/Int8ub)
class EF_NW(LinFixedEF): class EF_NW(LinFixedEF):
"""Section 7.9""" """Section 7.9"""
def __init__(self): def __init__(self):
super().__init__(fid='6f80', sfid=None, name='EF.NW', desc='Network Name', rec_len={8,8}) super().__init__(fid='6f80', sfid=None, name='EF.NW',
desc='Network Name', rec_len={8, 8})
self._construct = GsmString(8) self._construct = GsmString(8)
class EF_Switching(LinFixedEF): class EF_Switching(LinFixedEF):
"""Section 8.4""" """Section 8.4"""
def __init__(self, fid, name, desc): def __init__(self, fid, name, desc):
super().__init__(fid=fid, sfid=None, name=name, desc=desc, rec_len={6,6}) super().__init__(fid=fid, sfid=None,
name=name, desc=desc, rec_len={6, 6})
self._construct = Struct('next_table_type'/NextTableType, self._construct = Struct('next_table_type'/NextTableType,
'id_of_next_table'/HexAdapter(Bytes(2)), 'id_of_next_table'/HexAdapter(Bytes(2)),
'decision_value'/BcdAdapter(Bytes(2)), 'decision_value'/BcdAdapter(Bytes(2)),
'string_table_index'/Int8ub) 'string_table_index'/Int8ub)
class EF_Predefined(LinFixedEF): class EF_Predefined(LinFixedEF):
"""Section 8.5""" """Section 8.5"""
def __init__(self, fid, name, desc): def __init__(self, fid, name, desc):
super().__init__(fid=fid, sfid=None, name=name, desc=desc, rec_len={3,3}) super().__init__(fid=fid, sfid=None,
name=name, desc=desc, rec_len={3, 3})
# header and other records have different structure. WTF !?! # header and other records have different structure. WTF !?!
self._construct = Struct('next_table_type'/NextTableType, self._construct = Struct('next_table_type'/NextTableType,
'id_of_next_table'/HexAdapter(Bytes(2)), 'id_of_next_table'/HexAdapter(Bytes(2)),
@@ -212,8 +248,10 @@ class EF_Predefined(LinFixedEF):
'string_table_index1'/Int8ub) 'string_table_index1'/Int8ub)
# TODO: predefined value n, ... # TODO: predefined value n, ...
class EF_DialledVals(TransparentEF): class EF_DialledVals(TransparentEF):
"""Section 8.6""" """Section 8.6"""
def __init__(self, fid, name, desc): def __init__(self, fid, name, desc):
super().__init__(fid=fid, sfid=None, name=name, desc=desc, size={4, 4}) super().__init__(fid=fid, sfid=None, name=name, desc=desc, size={4, 4})
self._construct = Struct('next_table_type'/NextTableType, self._construct = Struct('next_table_type'/NextTableType,
@@ -238,18 +276,31 @@ class DF_EIRENE(CardDF):
EF_Switching(fid='6f8e', name='EF.CT', desc='Call Type'), EF_Switching(fid='6f8e', name='EF.CT', desc='Call Type'),
EF_Switching(fid='6f8f', name='EF.SC', desc='Short Code'), EF_Switching(fid='6f8f', name='EF.SC', desc='Short Code'),
EF_Predefined(fid='6f88', name='EF.FC', desc='Function Code'), EF_Predefined(fid='6f88', name='EF.FC', desc='Function Code'),
EF_Predefined(fid='6f89', name='EF.Service', desc='VGCS/VBS Service Code'), EF_Predefined(fid='6f89', name='EF.Service',
EF_Predefined(fid='6f8a', name='EF.Call', desc='First digit of the group ID'), desc='VGCS/VBS Service Code'),
EF_Predefined(fid='6f8b', name='EF.FctTeam', desc='Call Type 6 Team Type + Team member function'), EF_Predefined(fid='6f8a', name='EF.Call',
EF_Predefined(fid='6f92', name='EF.Controller', desc='Call Type 7 Controller function code'), desc='First digit of the group ID'),
EF_Predefined(fid='6f8c', name='EF.Gateway', desc='Access to external networks'), EF_Predefined(fid='6f8b', name='EF.FctTeam',
EF_DialledVals(fid='6f81', name='EF.5to8digits', desc='Call Type 2 User Identity Number length'), desc='Call Type 6 Team Type + Team member function'),
EF_DialledVals(fid='6f82', name='EF.2digits', desc='2 digits input'), EF_Predefined(fid='6f92', name='EF.Controller',
EF_DialledVals(fid='6f83', name='EF.8digits', desc='8 digits input'), desc='Call Type 7 Controller function code'),
EF_DialledVals(fid='6f84', name='EF.9digits', desc='9 digits input'), EF_Predefined(fid='6f8c', name='EF.Gateway',
EF_DialledVals(fid='6f85', name='EF.SSSSS', desc='Group call area input'), desc='Access to external networks'),
EF_DialledVals(fid='6f86', name='EF.LLLLL', desc='Location number Call Type 6'), EF_DialledVals(fid='6f81', name='EF.5to8digits',
EF_DialledVals(fid='6f91', name='EF.Location', desc='Location number Call Type 7'), desc='Call Type 2 User Identity Number length'),
EF_DialledVals(fid='6f87', name='EF.FreeNumber', desc='Free Number Call Type 0 and 8'), EF_DialledVals(fid='6f82', name='EF.2digits',
desc='2 digits input'),
EF_DialledVals(fid='6f83', name='EF.8digits',
desc='8 digits input'),
EF_DialledVals(fid='6f84', name='EF.9digits',
desc='9 digits input'),
EF_DialledVals(fid='6f85', name='EF.SSSSS',
desc='Group call area input'),
EF_DialledVals(fid='6f86', name='EF.LLLLL',
desc='Location number Call Type 6'),
EF_DialledVals(fid='6f91', name='EF.Location',
desc='Location number Call Type 7'),
EF_DialledVals(fid='6f87', name='EF.FreeNumber',
desc='Free Number Call Type 0 and 8'),
] ]
self.add_files(files) self.add_files(files)

View File

@@ -24,38 +24,56 @@ from pySim.filesystem import *
from pySim.tlv import * from pySim.tlv import *
# Table 91 + Section 8.2.1.2 # Table 91 + Section 8.2.1.2
class ApplicationId(BER_TLV_IE, tag=0x4f): class ApplicationId(BER_TLV_IE, tag=0x4f):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 # Table 91
class ApplicationLabel(BER_TLV_IE, tag=0x50): class ApplicationLabel(BER_TLV_IE, tag=0x50):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 + Section 5.3.1.2 # Table 91 + Section 5.3.1.2
class FileReference(BER_TLV_IE, tag=0x51): class FileReference(BER_TLV_IE, tag=0x51):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 # Table 91
class CommandApdu(BER_TLV_IE, tag=0x52): class CommandApdu(BER_TLV_IE, tag=0x52):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 # Table 91
class DiscretionaryData(BER_TLV_IE, tag=0x53): class DiscretionaryData(BER_TLV_IE, tag=0x53):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 # Table 91
class DiscretionaryTemplate(BER_TLV_IE, tag=0x73): class DiscretionaryTemplate(BER_TLV_IE, tag=0x73):
_construct = GreedyBytes _construct = GreedyBytes
# Table 91 + RFC1738 / RFC2396 # Table 91 + RFC1738 / RFC2396
class URL(BER_TLV_IE, tag=0x5f50): class URL(BER_TLV_IE, tag=0x5f50):
_construct = GreedyString('ascii') _construct = GreedyString('ascii')
# Table 91 # Table 91
class ApplicationRelatedDOSet(BER_TLV_IE, tag=0x61): class ApplicationRelatedDOSet(BER_TLV_IE, tag=0x61):
_construct = GreedyBytes _construct = GreedyBytes
# Section 8.2.1.3 Application Template # Section 8.2.1.3 Application Template
class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationId, ApplicationLabel, FileReference, class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationId, ApplicationLabel, FileReference,
CommandApdu, DiscretionaryData, DiscretionaryTemplate, URL, CommandApdu, DiscretionaryData, DiscretionaryTemplate, URL,
ApplicationRelatedDOSet]): ApplicationRelatedDOSet]):

View File

@@ -25,6 +25,7 @@ of a file or record in its JSON representation.
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
def js_path_find(js_dict, js_path): def js_path_find(js_dict, js_path):
"""Find/Match a JSON path within a given JSON-serializable dict. """Find/Match a JSON path within a given JSON-serializable dict.
Args: Args:
@@ -35,6 +36,7 @@ def js_path_find(js_dict, js_path):
jsonpath_expr = jsonpath_ng.parse(js_path) jsonpath_expr = jsonpath_ng.parse(js_path)
return jsonpath_expr.find(js_dict) return jsonpath_expr.find(js_dict)
def js_path_modify(js_dict, js_path, new_val): def js_path_modify(js_dict, js_path, new_val):
"""Find/Match a JSON path within a given JSON-serializable dict. """Find/Match a JSON path within a given JSON-serializable dict.
Args: Args:
@@ -45,4 +47,3 @@ def js_path_modify(js_dict, js_path, new_val):
jsonpath_expr = jsonpath_ng.parse(js_path) jsonpath_expr = jsonpath_ng.parse(js_path)
jsonpath_expr.find(js_dict) jsonpath_expr.find(js_dict)
jsonpath_expr.update(js_dict, new_val) jsonpath_expr.update(js_dict, new_val)

View File

@@ -27,6 +27,7 @@ from pySim.utils import all_subclasses
import abc import abc
import operator import operator
def _mf_select_test(scc: SimCardCommands, cla_byte: str, sel_ctrl: str) -> bool: def _mf_select_test(scc: SimCardCommands, cla_byte: str, sel_ctrl: str) -> bool:
cla_byte_bak = scc.cla_byte cla_byte_bak = scc.cla_byte
sel_ctrl_bak = scc.sel_ctrl sel_ctrl_bak = scc.sel_ctrl
@@ -45,12 +46,14 @@ def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool:
scc.sel_ctrl = sel_ctrl_bak scc.sel_ctrl = sel_ctrl_bak
return rc return rc
def match_uicc(scc: SimCardCommands) -> bool: def match_uicc(scc: SimCardCommands) -> bool:
""" Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the """ Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the
card is considered a UICC card. card is considered a UICC card.
""" """
return _mf_select_test(scc, "00", "0004") return _mf_select_test(scc, "00", "0004")
def match_sim(scc: SimCardCommands) -> bool: def match_sim(scc: SimCardCommands) -> bool:
""" Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card """ Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card
is also a simcard. This will be the case for most UICC cards, but there may is also a simcard. This will be the case for most UICC cards, but there may
@@ -58,6 +61,7 @@ def match_sim(scc:SimCardCommands) -> bool:
""" """
return _mf_select_test(scc, "a0", "0000") return _mf_select_test(scc, "a0", "0000")
class CardProfile(object): class CardProfile(object):
"""A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of """A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of
applications as well as profile-specific SW and shell commands. Every card has applications as well as profile-specific SW and shell commands. Every card has

View File

@@ -43,9 +43,11 @@ mac_length = {
1: 4 1: 4
} }
class EF_PIN(TransparentEF): class EF_PIN(TransparentEF):
def __init__(self, fid, name): def __init__(self, fid, name):
super().__init__(fid, name=name, desc='%s PIN file' % name) super().__init__(fid, name=name, desc='%s PIN file' % name)
def _decode_bin(self, raw_bin_data): def _decode_bin(self, raw_bin_data):
u = unpack('!BBB8s', raw_bin_data[:11]) u = unpack('!BBB8s', raw_bin_data[:11])
res = {'enabled': (True, False)[u[0] & 0x01], res = {'enabled': (True, False)[u[0] & 0x01],
@@ -65,9 +67,11 @@ class EF_PIN(TransparentEF):
res['puk'] = u2[2].hex() res['puk'] = u2[2].hex()
return res return res
class EF_MILENAGE_CFG(TransparentEF): class EF_MILENAGE_CFG(TransparentEF):
def __init__(self, fid='6f21', name='EF.MILENAGE_CFG', desc='Milenage connfiguration'): def __init__(self, fid='6f21', name='EF.MILENAGE_CFG', desc='Milenage connfiguration'):
super().__init__(fid, name=name, desc=desc) super().__init__(fid, name=name, desc=desc)
def _decode_bin(self, raw_bin_data): def _decode_bin(self, raw_bin_data):
u = unpack('!BBBBB16s16s16s16s16s', raw_bin_data) u = unpack('!BBBBB16s16s16s16s16s', raw_bin_data)
return {'r1': u[0], 'r2': u[1], 'r3': u[2], 'r4': u[3], 'r5': u[4], return {'r1': u[0], 'r2': u[1], 'r3': u[2], 'r4': u[3], 'r5': u[4],
@@ -78,9 +82,11 @@ class EF_MILENAGE_CFG(TransparentEF):
'c5': u[9].hex(), 'c5': u[9].hex(),
} }
class EF_0348_KEY(LinFixedEF): class EF_0348_KEY(LinFixedEF):
def __init__(self, fid='6f22', name='EF.0348_KEY', desc='TS 03.48 OTA Keys'): def __init__(self, fid='6f22', name='EF.0348_KEY', desc='TS 03.48 OTA Keys'):
super().__init__(fid, name=name, desc=desc, rec_len={27, 35}) super().__init__(fid, name=name, desc=desc, rec_len={27, 35})
def _decode_record_bin(self, raw_bin_data): def _decode_record_bin(self, raw_bin_data):
u = unpack('!BBB', raw_bin_data[0:3]) u = unpack('!BBB', raw_bin_data[0:3])
key_algo = (u[2] >> 6) & 1 key_algo = (u[2] >> 6) & 1
@@ -94,32 +100,40 @@ class EF_0348_KEY(LinFixedEF):
'key': raw_bin_data[3:key_length].hex() 'key': raw_bin_data[3:key_length].hex()
} }
class EF_0348_COUNT(LinFixedEF): class EF_0348_COUNT(LinFixedEF):
def __init__(self, fid='6f23', name='EF.0348_COUNT', desc='TS 03.48 OTA Counters'): def __init__(self, fid='6f23', name='EF.0348_COUNT', desc='TS 03.48 OTA Counters'):
super().__init__(fid, name=name, desc=desc, rec_len={7, 7}) super().__init__(fid, name=name, desc=desc, rec_len={7, 7})
def _decode_record_bin(self, raw_bin_data): def _decode_record_bin(self, raw_bin_data):
u = unpack('!BB5s', raw_bin_data) u = unpack('!BB5s', raw_bin_data)
return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2]} return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2]}
class EF_SIM_AUTH_COUNTER(TransparentEF): class EF_SIM_AUTH_COUNTER(TransparentEF):
def __init__(self, fid='af24', name='EF.SIM_AUTH_COUNTER'): def __init__(self, fid='af24', name='EF.SIM_AUTH_COUNTER'):
super().__init__(fid, name=name, desc='Number of remaining RUN GSM ALGORITHM executions') super().__init__(fid, name=name, desc='Number of remaining RUN GSM ALGORITHM executions')
self._construct = Struct('num_run_gsm_algo_remain'/Int32ub) self._construct = Struct('num_run_gsm_algo_remain'/Int32ub)
class EF_GP_COUNT(LinFixedEF): class EF_GP_COUNT(LinFixedEF):
def __init__(self, fid='6f26', name='EF.GP_COUNT', desc='GP SCP02 Counters'): def __init__(self, fid='6f26', name='EF.GP_COUNT', desc='GP SCP02 Counters'):
super().__init__(fid, name=name, desc=desc, rec_len={5, 5}) super().__init__(fid, name=name, desc=desc, rec_len={5, 5})
def _decode_record_bin(self, raw_bin_data): def _decode_record_bin(self, raw_bin_data):
u = unpack('!BBHB', raw_bin_data) u = unpack('!BBHB', raw_bin_data)
return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2], 'rfu': u[3]} return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2], 'rfu': u[3]}
class EF_GP_DIV_DATA(LinFixedEF): class EF_GP_DIV_DATA(LinFixedEF):
def __init__(self, fid='6f27', name='EF.GP_DIV_DATA', desc='GP SCP02 key diversification data'): def __init__(self, fid='6f27', name='EF.GP_DIV_DATA', desc='GP SCP02 key diversification data'):
super().__init__(fid, name=name, desc=desc, rec_len={12, 12}) super().__init__(fid, name=name, desc=desc, rec_len={12, 12})
def _decode_record_bin(self, raw_bin_data): def _decode_record_bin(self, raw_bin_data):
u = unpack('!BB8s', raw_bin_data) u = unpack('!BB8s', raw_bin_data)
return {'sec_domain': u[0], 'key_set_version': u[1], 'key_div_data': u[2].hex()} return {'sec_domain': u[0], 'key_set_version': u[1], 'key_div_data': u[2].hex()}
class EF_SIM_AUTH_KEY(TransparentEF): class EF_SIM_AUTH_KEY(TransparentEF):
def __init__(self, fid='6f20', name='EF.SIM_AUTH_KEY'): def __init__(self, fid='6f20', name='EF.SIM_AUTH_KEY'):
super().__init__(fid, name=name, desc='USIM authentication key') super().__init__(fid, name=name, desc='USIM authentication key')
@@ -129,10 +143,15 @@ class EF_SIM_AUTH_KEY(TransparentEF):
'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3)) 'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3))
self._construct = Struct('cfg'/CfgByte, self._construct = Struct('cfg'/CfgByte,
'key'/Bytes(16), 'key'/Bytes(16),
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)), 'op' /
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16)) If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes(
16)),
'opc' /
If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes(
16))
) )
class DF_SYSTEM(CardDF): class DF_SYSTEM(CardDF):
def __init__(self): def __init__(self):
super().__init__(fid='a515', name='DF.SYSTEM', desc='CardOS specifics') super().__init__(fid='a515', name='DF.SYSTEM', desc='CardOS specifics')
@@ -156,6 +175,7 @@ class DF_SYSTEM(CardDF):
def decode_select_response(self, resp_hex): def decode_select_response(self, resp_hex):
return pySim.ts_102_221.CardProfileUICC.decode_select_response(resp_hex) return pySim.ts_102_221.CardProfileUICC.decode_select_response(resp_hex)
class EF_USIM_SQN(TransparentEF): class EF_USIM_SQN(TransparentEF):
def __init__(self, fid='af30', name='EF.USIM_SQN'): def __init__(self, fid='af30', name='EF.USIM_SQN'):
super().__init__(fid, name=name, desc='SQN parameters for AKA') super().__init__(fid, name=name, desc='SQN parameters for AKA')
@@ -165,9 +185,11 @@ class EF_USIM_SQN(TransparentEF):
Flag2 = BitStruct('rfu'/BitsRFU(5), 'dont_clear_amf_for_macs'/Bit, Flag2 = BitStruct('rfu'/BitsRFU(5), 'dont_clear_amf_for_macs'/Bit,
'aus_concealed'/Bit, 'autn_concealed'/Bit) 'aus_concealed'/Bit, 'autn_concealed'/Bit)
self._construct = Struct('flag1'/Flag1, 'flag2'/Flag2, self._construct = Struct('flag1'/Flag1, 'flag2'/Flag2,
'delta_max'/BytesInteger(6), 'age_limit'/BytesInteger(6), 'delta_max' /
BytesInteger(6), 'age_limit'/BytesInteger(6),
'freshness'/GreedyRange(BytesInteger(6))) 'freshness'/GreedyRange(BytesInteger(6)))
class EF_USIM_AUTH_KEY(TransparentEF): class EF_USIM_AUTH_KEY(TransparentEF):
def __init__(self, fid='af20', name='EF.USIM_AUTH_KEY'): def __init__(self, fid='af20', name='EF.USIM_AUTH_KEY'):
super().__init__(fid, name=name, desc='USIM authentication key') super().__init__(fid, name=name, desc='USIM authentication key')
@@ -177,9 +199,15 @@ class EF_USIM_AUTH_KEY(TransparentEF):
'algorithm'/Enum(Nibble, milenage=4, sha1_aka=5, xor=15)) 'algorithm'/Enum(Nibble, milenage=4, sha1_aka=5, xor=15))
self._construct = Struct('cfg'/CfgByte, self._construct = Struct('cfg'/CfgByte,
'key'/Bytes(16), 'key'/Bytes(16),
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)), 'op' /
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16)) If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes(
16)),
'opc' /
If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes(
16))
) )
class EF_USIM_AUTH_KEY_2G(TransparentEF): class EF_USIM_AUTH_KEY_2G(TransparentEF):
def __init__(self, fid='af22', name='EF.USIM_AUTH_KEY_2G'): def __init__(self, fid='af22', name='EF.USIM_AUTH_KEY_2G'):
super().__init__(fid, name=name, desc='USIM authentication key in 2G context') super().__init__(fid, name=name, desc='USIM authentication key in 2G context')
@@ -189,31 +217,40 @@ class EF_USIM_AUTH_KEY_2G(TransparentEF):
'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3)) 'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3))
self._construct = Struct('cfg'/CfgByte, self._construct = Struct('cfg'/CfgByte,
'key'/Bytes(16), 'key'/Bytes(16),
'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)), 'op' /
'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16)) If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes(
16)),
'opc' /
If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes(
16))
) )
class EF_GBA_SK(TransparentEF): class EF_GBA_SK(TransparentEF):
def __init__(self, fid='af31', name='EF.GBA_SK'): def __init__(self, fid='af31', name='EF.GBA_SK'):
super().__init__(fid, name=name, desc='Secret key for GBA key derivation') super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
self._construct = GreedyBytes self._construct = GreedyBytes
class EF_GBA_REC_LIST(TransparentEF): class EF_GBA_REC_LIST(TransparentEF):
def __init__(self, fid='af32', name='EF.GBA_REC_LIST'): def __init__(self, fid='af32', name='EF.GBA_REC_LIST'):
super().__init__(fid, name=name, desc='Secret key for GBA key derivation') super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
# integers representing record numbers in EF-GBANL # integers representing record numbers in EF-GBANL
self._construct = GreedyRange(Int8ub) self._construct = GreedyRange(Int8ub)
class EF_GBA_INT_KEY(LinFixedEF): class EF_GBA_INT_KEY(LinFixedEF):
def __init__(self, fid='af33', name='EF.GBA_INT_KEY'): def __init__(self, fid='af33', name='EF.GBA_INT_KEY'):
super().__init__(fid, name=name, desc='Secret key for GBA key derivation', rec_len={32,32}) super().__init__(fid, name=name,
desc='Secret key for GBA key derivation', rec_len={32, 32})
self._construct = GreedyBytes self._construct = GreedyBytes
class SysmocomSJA2(CardModel): class SysmocomSJA2(CardModel):
_atrs = ["3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9", _atrs = ["3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9",
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2", "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2",
"3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5"] "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5"]
@classmethod @classmethod
def add_files(cls, rs: RuntimeState): def add_files(cls, rs: RuntimeState):
"""Add sysmocom SJA2 specific files to given RuntimeState.""" """Add sysmocom SJA2 specific files to given RuntimeState."""

View File

@@ -32,6 +32,7 @@ from pySim.exceptions import *
import inspect import inspect
import abc import abc
class TlvMeta(abc.ABCMeta): class TlvMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass. """Metaclass which we use to set some class variables at the time of defining a subclass.
This allows us to create subclasses for each TLV/IE type, where the class represents fixed This allows us to create subclasses for each TLV/IE type, where the class represents fixed
@@ -54,6 +55,7 @@ class TlvMeta(abc.ABCMeta):
x.nested_collection_cls = cls x.nested_collection_cls = cls
return x return x
class TlvCollectionMeta(abc.ABCMeta): class TlvCollectionMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass. """Metaclass which we use to set some class variables at the time of defining a subclass.
This allows us to create subclasses for each Collection type, where the class represents fixed This allows us to create subclasses for each Collection type, where the class represents fixed
@@ -72,6 +74,7 @@ class Transcodable(abc.ABC):
* via a 'construct' object stored in a derived class' _construct variable, or * via a 'construct' object stored in a derived class' _construct variable, or
* via a 'construct' object stored in an instance _construct variable, or * via a 'construct' object stored in an instance _construct variable, or
* via a derived class' _{to,from}_bytes() methods.""" * via a derived class' _{to,from}_bytes() methods."""
def __init__(self): def __init__(self):
self.encoded = None self.encoded = None
self.decoded = None self.decoded = None
@@ -113,6 +116,7 @@ class Transcodable(abc.ABC):
def _from_bytes(self, do: bytes): def _from_bytes(self, do: bytes):
raise NotImplementedError raise NotImplementedError
class IE(Transcodable, metaclass=TlvMeta): class IE(Transcodable, metaclass=TlvMeta):
# we specify the metaclass so any downstream subclasses will automatically use it # we specify the metaclass so any downstream subclasses will automatically use it
"""Base class for various Information Elements. We understand the notion of a hierarchy """Base class for various Information Elements. We understand the notion of a hierarchy
@@ -188,6 +192,7 @@ class IE(Transcodable, metaclass=TlvMeta):
class TLV_IE(IE): class TLV_IE(IE):
"""Abstract base class for various TLV type Information Elements.""" """Abstract base class for various TLV type Information Elements."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -240,6 +245,7 @@ class TLV_IE(IE):
class BER_TLV_IE(TLV_IE): class BER_TLV_IE(TLV_IE):
"""TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2.""" """TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -264,6 +270,7 @@ class BER_TLV_IE(TLV_IE):
class COMPR_TLV_IE(TLV_IE): class COMPR_TLV_IE(TLV_IE):
"""TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220.""" """TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.comprehension = False self.comprehension = False
@@ -294,6 +301,7 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
of each DO.""" of each DO."""
# this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses # this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
possible_nested = [] possible_nested = []
def __init__(self, desc=None, **kwargs): def __init__(self, desc=None, **kwargs):
self.desc = desc self.desc = desc
#print("possible_nested: ", self.possible_nested) #print("possible_nested: ", self.possible_nested)

View File

@@ -29,6 +29,7 @@ from pySim.utils import sw_match, b2h, h2b, i2h
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
class ApduTracer: class ApduTracer:
def trace_command(self, cmd): def trace_command(self, cmd):
pass pass
@@ -105,7 +106,7 @@ class LinkBase(abc.ABC):
""" """
data, sw = self.send_apdu_raw(pdu) data, sw = self.send_apdu_raw(pdu)
# When whe have sent the first APDU, the SW may indicate that there are response bytes # When we have sent the first APDU, the SW may indicate that there are response bytes
# available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where # available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
# xx is the number of response bytes available. # xx is the number of response bytes available.
# See also: # See also:
@@ -185,11 +186,13 @@ class LinkBase(abc.ABC):
Returns: Returns:
Tuple of (decoded_data, sw) Tuple of (decoded_data, sw)
""" """
(rsp, sw) = self.send_apdu_constr(cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr) (rsp, sw) = self.send_apdu_constr(cla, ins,
p1, p2, cmd_constr, cmd_data, resp_constr)
if not sw_match(sw, sw_exp): if not sw_match(sw, sw_exp):
raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter) raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter)
return (rsp, sw) return (rsp, sw)
def argparse_add_reader_args(arg_parser): def argparse_add_reader_args(arg_parser):
"""Add all reader related arguments to the given argparse.Argumentparser instance.""" """Add all reader related arguments to the given argparse.Argumentparser instance."""
serial_group = arg_parser.add_argument_group('Serial Reader') serial_group = arg_parser.add_argument_group('Serial Reader')
@@ -214,6 +217,7 @@ def argparse_add_reader_args(arg_parser):
return arg_parser return arg_parser
def init_reader(opts, **kwargs) -> Optional[LinkBase]: def init_reader(opts, **kwargs) -> Optional[LinkBase]:
""" """
Init card reader driver Init card reader driver
@@ -231,15 +235,18 @@ def init_reader(opts, **kwargs) -> Optional[LinkBase]:
elif opts.modem_dev is not None: elif opts.modem_dev is not None:
print("Using modem for Generic SIM Access (3GPP TS 27.007)") print("Using modem for Generic SIM Access (3GPP TS 27.007)")
from pySim.transport.modem_atcmd import ModemATCommandLink from pySim.transport.modem_atcmd import ModemATCommandLink
sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs) sl = ModemATCommandLink(
device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs)
else: # Serial reader is default else: # Serial reader is default
print("Using serial reader interface") print("Using serial reader interface")
from pySim.transport.serial import SerialSimLink from pySim.transport.serial import SerialSimLink
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate, **kwargs) sl = SerialSimLink(device=opts.device,
baudrate=opts.baudrate, **kwargs)
return sl return sl
except Exception as e: except Exception as e:
if str(e): if str(e):
print("Card reader initialization failed with exception:\n" + str(e)) print("Card reader initialization failed with exception:\n" + str(e))
else: else:
print("Card reader initialization failed with an exception of type:\n" + str(type(e))) print(
"Card reader initialization failed with an exception of type:\n" + str(type(e)))
return None return None

View File

@@ -25,6 +25,7 @@ from pySim.transport import LinkBase
from pySim.exceptions import * from pySim.exceptions import *
from pySim.utils import h2b, b2h from pySim.utils import h2b, b2h
class L1CTLMessage(object): class L1CTLMessage(object):
# Every (encoded) L1CTL message has the following structure: # Every (encoded) L1CTL message has the following structure:
@@ -42,6 +43,7 @@ class L1CTLMessage(object):
def gen_msg(self): def gen_msg(self):
return struct.pack("!H", len(self.data)) + self.data return struct.pack("!H", len(self.data)) + self.data
class L1CTLMessageReset(L1CTLMessage): class L1CTLMessageReset(L1CTLMessage):
# L1CTL message types # L1CTL message types
@@ -58,6 +60,7 @@ class L1CTLMessageReset(L1CTLMessage):
super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ) super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ)
self.data += struct.pack("Bxxx", type) self.data += struct.pack("Bxxx", type)
class L1CTLMessageSIM(L1CTLMessage): class L1CTLMessageSIM(L1CTLMessage):
# SIM related message types # SIM related message types
@@ -68,6 +71,7 @@ class L1CTLMessageSIM(L1CTLMessage):
super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ) super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ)
self.data += pdu self.data += pdu
class CalypsoSimLink(LinkBase): class CalypsoSimLink(LinkBase):
"""Transport Link for Calypso based phones.""" """Transport Link for Calypso based phones."""
@@ -75,7 +79,8 @@ class CalypsoSimLink(LinkBase):
super().__init__(**kwargs) super().__init__(**kwargs)
# Make sure that a given socket path exists # Make sure that a given socket path exists
if not os.path.exists(sock_path): if not os.path.exists(sock_path):
raise ReaderError("There is no such ('%s') UNIX socket" % sock_path) raise ReaderError(
"There is no such ('%s') UNIX socket" % sock_path)
print("Connecting to osmocon at '%s'..." % sock_path) print("Connecting to osmocon at '%s'..." % sock_path)

View File

@@ -27,8 +27,10 @@ from pySim.exceptions import *
# HACK: if somebody needs to debug this thing # HACK: if somebody needs to debug this thing
# log.root.setLevel(log.DEBUG) # log.root.setLevel(log.DEBUG)
class ModemATCommandLink(LinkBase): class ModemATCommandLink(LinkBase):
"""Transport Link for 3GPP TS 27.007 compliant modems.""" """Transport Link for 3GPP TS 27.007 compliant modems."""
def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 115200, **kwargs): def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 115200, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self._sl = serial.Serial(device, baudrate, timeout=5) self._sl = serial.Serial(device, baudrate, timeout=5)
@@ -82,7 +84,8 @@ class ModemATCommandLink(LinkBase):
break break
time.sleep(patience) time.sleep(patience)
its += 1 its += 1
log.debug('Command took %0.6fs (%d cycles a %fs)', time.time() - t_start, its, patience) log.debug('Command took %0.6fs (%d cycles a %fs)',
time.time() - t_start, its, patience)
if self._echo: if self._echo:
# Skip echo chars # Skip echo chars
@@ -113,7 +116,8 @@ class ModemATCommandLink(LinkBase):
elif result[-1] == b'AT\r\r\nOK': elif result[-1] == b'AT\r\r\nOK':
self._echo = True self._echo = True
return return
raise ReaderError('Interface \'%s\' does not respond to \'AT\' command' % self._device) raise ReaderError(
'Interface \'%s\' does not respond to \'AT\' command' % self._device)
def reset_card(self): def reset_card(self):
# Reset the modem, just to be sure # Reset the modem, just to be sure

View File

@@ -47,7 +47,8 @@ class PcscSimLink(LinkBase):
return return
def wait_for_card(self, timeout: int = None, newcardonly: bool = False): def wait_for_card(self, timeout: int = None, newcardonly: bool = False):
cr = CardRequest(readers=[self._reader], timeout=timeout, newcardonly=newcardonly) cr = CardRequest(readers=[self._reader],
timeout=timeout, newcardonly=newcardonly)
try: try:
cr.waitforcard() cr.waitforcard()
except CardRequestTimeoutException: except CardRequestTimeoutException:

View File

@@ -168,7 +168,8 @@ class SerialSimLink(LinkBase):
self._sl.write(b) self._sl.write(b)
r = self._sl.read() r = self._sl.read()
if r != b: # TX and RX are tied, so we must clear the echo if r != b: # TX and RX are tied, so we must clear the echo
raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (ord(b), '%02x'%ord(r) if r else '(nil)')) raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (
ord(b), '%02x' % ord(r) if r else '(nil)'))
def _tx_string(self, s): def _tx_string(self, s):
"""This is only safe if it's guaranteed the card won't send any data """This is only safe if it's guaranteed the card won't send any data
@@ -176,7 +177,8 @@ class SerialSimLink(LinkBase):
self._sl.write(s) self._sl.write(s)
r = self._sl.read(len(s)) r = self._sl.read(len(s))
if r != s: # TX and RX are tied, so we must clear the echo if r != s: # TX and RX are tied, so we must clear the echo
raise ProtocolError("Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r))) raise ProtocolError(
"Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r)))
def _rx_byte(self): def _rx_byte(self):
return self._sl.read() return self._sl.read()

View File

@@ -110,6 +110,8 @@ FCP_Proprietary_TLV_MAP = {
} }
# ETSI TS 102 221 11.1.1.4.3 # ETSI TS 102 221 11.1.1.4.3
def interpret_file_descriptor(in_hex): def interpret_file_descriptor(in_hex):
in_bin = h2b(in_hex) in_bin = h2b(in_hex)
out = {} out = {}
@@ -140,6 +142,8 @@ def interpret_file_descriptor(in_hex):
return out return out
# ETSI TS 102 221 11.1.1.4.9 # ETSI TS 102 221 11.1.1.4.9
def interpret_life_cycle_sts_int(in_hex): def interpret_life_cycle_sts_int(in_hex):
lcsi = int(in_hex, 16) lcsi = int(in_hex, 16)
if lcsi == 0x00: if lcsi == 0x00:
@@ -157,6 +161,7 @@ def interpret_life_cycle_sts_int(in_hex):
else: else:
return in_hex return in_hex
# ETSI TS 102 221 11.1.1.4.10 # ETSI TS 102 221 11.1.1.4.10
FCP_Pin_Status_TLV_MAP = { FCP_Pin_Status_TLV_MAP = {
'90': 'ps_do', '90': 'ps_do',
@@ -164,12 +169,14 @@ FCP_Pin_Status_TLV_MAP = {
'83': 'key_reference', '83': 'key_reference',
} }
def interpret_ps_templ_do(in_hex): def interpret_ps_templ_do(in_hex):
# cannot use the 'TLV' parser due to repeating tags # cannot use the 'TLV' parser due to repeating tags
#psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP) #psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP)
# return psdo_tlv.parse(in_hex) # return psdo_tlv.parse(in_hex)
return in_hex return in_hex
# 'interpreter' functions for each tag # 'interpreter' functions for each tag
FCP_interpreter_map = { FCP_interpreter_map = {
'80': lambda x: int(x, 16), '80': lambda x: int(x, 16),
@@ -186,6 +193,8 @@ FCP_prorietary_interpreter_map = {
# accept unknown tags. It also doesn't raise a specific exception type but # accept unknown tags. It also doesn't raise a specific exception type but
# just the generic ValueError, so we cannot ignore those either. Instead, # just the generic ValueError, so we cannot ignore those either. Instead,
# we insert a dict entry for every possible proprietary tag permitted # we insert a dict entry for every possible proprietary tag permitted
def fixup_fcp_proprietary_tlv_map(tlv_map): def fixup_fcp_proprietary_tlv_map(tlv_map):
if 'D0' in tlv_map: if 'D0' in tlv_map:
return return
@@ -204,6 +213,7 @@ def tlv_key_replace(inmap, indata):
return key return key
return {newkey(inmap, d[0]): d[1] for d in indata.items()} return {newkey(inmap, d[0]): d[1] for d in indata.items()}
def tlv_val_interpret(inmap, indata): def tlv_val_interpret(inmap, indata):
def newval(inmap, key, val): def newval(inmap, key, val):
if key in inmap: if key in inmap:
@@ -214,6 +224,7 @@ def tlv_val_interpret(inmap, indata):
# ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4 # ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4
class _AM_DO_DF(DataObject): class _AM_DO_DF(DataObject):
def __init__(self): def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80) super().__init__('access_mode', 'Access Mode', tag=0x80)
@@ -262,6 +273,7 @@ class _AM_DO_DF(DataObject):
class _AM_DO_EF(DataObject): class _AM_DO_EF(DataObject):
"""ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31""" """ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31"""
def __init__(self): def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80) super().__init__('access_mode', 'Access Mode', tag=0x80)
@@ -306,8 +318,10 @@ class _AM_DO_EF(DataObject):
val |= 0x01 val |= 0x01
return val.to_bytes(1, 'big') return val.to_bytes(1, 'big')
class _AM_DO_CHDR(DataObject): class _AM_DO_CHDR(DataObject):
"""Command Header Access Mode DO according to ISO 7816-4 Table 32.""" """Command Header Access Mode DO according to ISO 7816-4 Table 32."""
def __init__(self, tag): def __init__(self, tag):
super().__init__('command_header', 'Command Header Description', tag=tag) super().__init__('command_header', 'Command Header Description', tag=tag)
@@ -353,6 +367,7 @@ class _AM_DO_CHDR(DataObject):
res.append(self.decoded['P2']) res.append(self.decoded['P2'])
return res return res
AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[ AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[
_AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84), _AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84),
_AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88), _AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88),
@@ -395,10 +410,13 @@ pin_names = bidict({
0x8e: 'ADM10', 0x8e: 'ADM10',
}) })
class CRT_DO(DataObject): class CRT_DO(DataObject):
"""Control Reference Template as per TS 102 221 9.5.1""" """Control Reference Template as per TS 102 221 9.5.1"""
def __init__(self): def __init__(self):
super().__init__('control_reference_template', 'Control Reference Template', tag=0xA4) super().__init__('control_reference_template',
'Control Reference Template', tag=0xA4)
def from_bytes(self, do: bytes): def from_bytes(self, do: bytes):
"""Decode a Control Reference Template DO.""" """Decode a Control Reference Template DO."""
@@ -407,7 +425,8 @@ class CRT_DO(DataObject):
if do[0] != 0x83 or do[1] != 0x01: if do[0] != 0x83 or do[1] != 0x01:
raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do) raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do)
if do[3:] != b'\x95\x01\x08': if do[3:] != b'\x95\x01\x08':
raise ValueError('Unsupported Usage Qualifier Tag or Len in CRT DO %s', do) raise ValueError(
'Unsupported Usage Qualifier Tag or Len in CRT DO %s', do)
self.encoded = do[0:6] self.encoded = do[0:6]
self.decoded = pin_names[do[2]] self.decoded = pin_names[do[2]]
return do[6:] return do[6:]
@@ -417,6 +436,8 @@ class CRT_DO(DataObject):
return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08' return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08'
# ISO7816-4 9.3.3 Table 33 # ISO7816-4 9.3.3 Table 33
class SecCondByte_DO(DataObject): class SecCondByte_DO(DataObject):
def __init__(self, tag=0x9d): def __init__(self, tag=0x9d):
super().__init__('security_condition_byte', tag=tag) super().__init__('security_condition_byte', tag=tag)
@@ -470,14 +491,18 @@ class SecCondByte_DO(DataObject):
raise ValueError('Unknown condition %s' % c) raise ValueError('Unknown condition %s' % c)
return res.to_bytes(1, 'big') return res.to_bytes(1, 'big')
Always_DO = TL0_DataObject('always', 'Always', 0x90) Always_DO = TL0_DataObject('always', 'Always', 0x90)
Never_DO = TL0_DataObject('never', 'Never', 0x97) Never_DO = TL0_DataObject('never', 'Never', 0x97)
class Nested_DO(DataObject): class Nested_DO(DataObject):
"""A DO that nests another DO/Choice/Sequence""" """A DO that nests another DO/Choice/Sequence"""
def __init__(self, name, tag, choice): def __init__(self, name, tag, choice):
super().__init__(name, tag=tag) super().__init__(name, tag=tag)
self.children = choice self.children = choice
def from_bytes(self, binary: bytes) -> list: def from_bytes(self, binary: bytes) -> list:
remainder = binary remainder = binary
self.decoded = [] self.decoded = []
@@ -485,10 +510,12 @@ class Nested_DO(DataObject):
rc, remainder = self.children.decode(remainder) rc, remainder = self.children.decode(remainder)
self.decoded.append(rc) self.decoded.append(rc)
return self.decoded return self.decoded
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
encoded = [self.children.encode(d) for d in self.decoded] encoded = [self.children.encode(d) for d in self.decoded]
return b''.join(encoded) return b''.join(encoded)
OR_Template = DataObjectChoice('or_template', 'OR-Template', OR_Template = DataObjectChoice('or_template', 'OR-Template',
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()]) members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
OR_DO = Nested_DO('or', 0xa0, OR_Template) OR_DO = Nested_DO('or', 0xa0, OR_Template)
@@ -503,6 +530,8 @@ SC_DO = DataObjectChoice('security_condition', 'Security Condition',
OR_DO, AND_DO, NOT_DO]) OR_DO, AND_DO, NOT_DO])
# TS 102 221 Section 13.1 # TS 102 221 Section 13.1
class EF_DIR(LinFixedEF): class EF_DIR(LinFixedEF):
class ApplicationLabel(BER_TLV_IE, tag=0x50): class ApplicationLabel(BER_TLV_IE, tag=0x50):
# TODO: UCS-2 coding option as per Annex A of TS 102 221 # TODO: UCS-2 coding option as per Annex A of TS 102 221
@@ -522,6 +551,8 @@ class EF_DIR(LinFixedEF):
self._tlv = EF_DIR.ApplicationTemplate self._tlv = EF_DIR.ApplicationTemplate
# TS 102 221 Section 13.2 # TS 102 221 Section 13.2
class EF_ICCID(TransparentEF): class EF_ICCID(TransparentEF):
def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'): def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10, 10}) super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10, 10})
@@ -533,14 +564,19 @@ class EF_ICCID(TransparentEF):
return enc_iccid(abstract['iccid']) return enc_iccid(abstract['iccid'])
# TS 102 221 Section 13.3 # TS 102 221 Section 13.3
class EF_PL(TransRecEF): class EF_PL(TransRecEF):
def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'): def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=2, size={2,None}) super().__init__(fid, sfid=sfid, name=name,
desc=desc, rec_len=2, size={2, None})
def _decode_record_bin(self, bin_data): def _decode_record_bin(self, bin_data):
if bin_data == b'\xff\xff': if bin_data == b'\xff\xff':
return None return None
else: else:
return bin_data.decode('ascii') return bin_data.decode('ascii')
def _encode_record_bin(self, in_json): def _encode_record_bin(self, in_json):
if in_json is None: if in_json is None:
return b'\xff\xff' return b'\xff\xff'
@@ -629,8 +665,11 @@ class EF_ARR(LinFixedEF):
class EF_UMPC(TransparentEF): class EF_UMPC(TransparentEF):
def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'): def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5, 5}) super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5, 5})
addl_info = FlagsEnum(Byte, req_inc_idle_current=1, support_uicc_suspend=2) addl_info = FlagsEnum(Byte, req_inc_idle_current=1,
self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info) support_uicc_suspend=2)
self._construct = Struct(
'max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
class CardProfileUICC(CardProfile): class CardProfileUICC(CardProfile):
@@ -714,7 +753,8 @@ class CardProfileUICC(CardProfile):
}, },
} }
super().__init__(name, desc='ETSI TS 102 221', cla="00", sel_ctrl="0004", files_in_mf=files, sw=sw) super().__init__(name, desc='ETSI TS 102 221', cla="00",
sel_ctrl="0004", files_in_mf=files, sw=sw)
@staticmethod @staticmethod
def decode_select_response(resp_hex: str) -> object: def decode_select_response(resp_hex: str) -> object:
@@ -741,6 +781,7 @@ class CardProfileUICC(CardProfile):
def match_with_card(scc: SimCardCommands) -> bool: def match_with_card(scc: SimCardCommands) -> bool:
return match_uicc(scc) return match_uicc(scc)
class CardProfileUICCSIM(CardProfileUICC): class CardProfileUICCSIM(CardProfileUICC):
"""Same as above, but including 2G SIM support""" """Same as above, but including 2G SIM support"""

View File

@@ -27,6 +27,21 @@ Various constants from 3GPP TS 31.102 V16.6.0
# #
# Mapping between USIM Service Number and its description # Mapping between USIM Service Number and its description
import pySim.ts_102_221
from pySim.ts_51_011 import EF_ACMmax, EF_AAeM, EF_eMLPP, EF_CMI, EF_PNN
from pySim.ts_51_011 import EF_MMSN, EF_MMSICP, EF_MMSUP, EF_MMSUCP, EF_VGCS, EF_VGCSS, EF_NIA
from pySim.ts_51_011 import EF_SMSR, EF_DCK, EF_EXT, EF_CNL, EF_OPL, EF_MBI, EF_MWIS
from pySim.ts_51_011 import EF_CBMID, EF_CBMIR, EF_ADN, EF_SMS, EF_MSISDN, EF_SMSP, EF_SMSS
from pySim.ts_51_011 import EF_IMSI, EF_xPLMNwAcT, EF_SPN, EF_CBMI, EF_ACC, EF_PLMNsel
from pySim.ts_102_221 import EF_ARR
from pySim.tlv import *
from pySim.filesystem import *
from pySim.construct import *
from construct import Optional as COptional
from construct import *
from typing import Tuple
from struct import unpack, pack
import enum
EF_UST_map = { EF_UST_map = {
1: 'Local Phone Book', 1: 'Local Phone Book',
2: 'Fixed Dialling Numbers (FDN)', 2: 'Fixed Dialling Numbers (FDN)',
@@ -287,24 +302,9 @@ EF_USIM_ADF_map = {
# ADF.USIM # ADF.USIM
###################################################################### ######################################################################
import enum
from struct import unpack, pack
from typing import Tuple
from construct import *
from construct import Optional as COptional
from pySim.construct import *
from pySim.filesystem import *
from pySim.tlv import *
from pySim.ts_102_221 import EF_ARR
from pySim.ts_51_011 import EF_IMSI, EF_xPLMNwAcT, EF_SPN, EF_CBMI, EF_ACC, EF_PLMNsel
from pySim.ts_51_011 import EF_CBMID, EF_CBMIR, EF_ADN, EF_SMS, EF_MSISDN, EF_SMSP, EF_SMSS
from pySim.ts_51_011 import EF_SMSR, EF_DCK, EF_EXT, EF_CNL, EF_OPL, EF_MBI, EF_MWIS
from pySim.ts_51_011 import EF_MMSN, EF_MMSICP, EF_MMSUP, EF_MMSUCP, EF_VGCS, EF_VGCSS, EF_NIA
from pySim.ts_51_011 import EF_ACMmax, EF_AAeM, EF_eMLPP, EF_CMI, EF_PNN
import pySim.ts_102_221
# 3GPP TS 31.102 Section 4.4.11.4 (EF_5GS3GPPNSC) # 3GPP TS 31.102 Section 4.4.11.4 (EF_5GS3GPPNSC)
class EF_5GS3GPPNSC(LinFixedEF): class EF_5GS3GPPNSC(LinFixedEF):
class NgKSI(BER_TLV_IE, tag=0x80): class NgKSI(BER_TLV_IE, tag=0x80):
_construct = Int8ub _construct = Int8ub
@@ -338,6 +338,8 @@ class EF_5GS3GPPNSC(LinFixedEF):
self._tlv = EF_5GS3GPPNSC.FiveGSNasSecurityContext self._tlv = EF_5GS3GPPNSC.FiveGSNasSecurityContext
# 3GPP TS 31.102 Section 4.4.11.6 # 3GPP TS 31.102 Section 4.4.11.6
class EF_5GAUTHKEYS(TransparentEF): class EF_5GAUTHKEYS(TransparentEF):
class K_AUSF(BER_TLV_IE, tag=0x80): class K_AUSF(BER_TLV_IE, tag=0x80):
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
@@ -354,24 +356,32 @@ class EF_5GAUTHKEYS(TransparentEF):
self._tlv = EF_5GAUTHKEYS.FiveGAuthKeys self._tlv = EF_5GAUTHKEYS.FiveGAuthKeys
# 3GPP TS 31.102 Section 4.4.11.8 # 3GPP TS 31.102 Section 4.4.11.8
class ProtSchemeIdList(BER_TLV_IE, tag=0xa0): class ProtSchemeIdList(BER_TLV_IE, tag=0xa0):
# FIXME: 3GPP TS 24.501 Protection Scheme Identifier # FIXME: 3GPP TS 24.501 Protection Scheme Identifier
# repeated sequence of (id, index) tuples # repeated sequence of (id, index) tuples
_construct = GreedyRange(Struct('id'/Enum(Byte, null=0, A=1, B=2), 'index'/Int8ub)) _construct = GreedyRange(
Struct('id'/Enum(Byte, null=0, A=1, B=2), 'index'/Int8ub))
class HomeNetPubKeyId(BER_TLV_IE, tag=0x80): class HomeNetPubKeyId(BER_TLV_IE, tag=0x80):
# 3GPP TS 24.501 / 3GPP TS 23.003 # 3GPP TS 24.501 / 3GPP TS 23.003
_construct = Int8ub _construct = Int8ub
class HomeNetPubKey(BER_TLV_IE, tag=0x81): class HomeNetPubKey(BER_TLV_IE, tag=0x81):
# FIXME: RFC 5480 # FIXME: RFC 5480
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class HomeNetPubKeyList(BER_TLV_IE, tag=0xa1, class HomeNetPubKeyList(BER_TLV_IE, tag=0xa1,
nested=[HomeNetPubKeyId, HomeNetPubKey]): nested=[HomeNetPubKeyId, HomeNetPubKey]):
pass pass
# 3GPP TS 31.102 Section 4.4.11.6 # 3GPP TS 31.102 Section 4.4.11.6
class SUCI_CalcInfo(TLV_IE_Collection, nested=[ProtSchemeIdList, HomeNetPubKeyList]): class SUCI_CalcInfo(TLV_IE_Collection, nested=[ProtSchemeIdList, HomeNetPubKeyList]):
pass pass
@@ -414,7 +424,8 @@ class EF_SUCI_Calc_Info(TransparentEF):
return out_bytes return out_bytes
def _encode_hex(self, in_json): def _encode_hex(self, in_json):
out_bytes = self._encode_prot_scheme_id_list(in_json['prot_scheme_id_list']) out_bytes = self._encode_prot_scheme_id_list(
in_json['prot_scheme_id_list'])
out_bytes += self._encode_hnet_pubkey_list(in_json['hnet_pubkey_list']) out_bytes += self._encode_hnet_pubkey_list(in_json['hnet_pubkey_list'])
return "".join(["%02X" % i for i in out_bytes]) return "".join(["%02X" % i for i in out_bytes])
@@ -447,7 +458,8 @@ class EF_SUCI_Calc_Info(TransparentEF):
print("missing Home Network Public Key Identifier tag") print("missing Home Network Public Key Identifier tag")
return {} return {}
pos += 1 pos += 1
hnet_pubkey_id_len = in_bytes[pos] # TODO might be more than 1 byte? # TODO might be more than 1 byte?
hnet_pubkey_id_len = in_bytes[pos]
pos += 1 pos += 1
hnet_pubkey_id = in_bytes[pos:pos+hnet_pubkey_id_len][0] hnet_pubkey_id = in_bytes[pos:pos+hnet_pubkey_id_len][0]
pos += hnet_pubkey_id_len pos += hnet_pubkey_id_len
@@ -482,7 +494,8 @@ class EF_SUCI_Calc_Info(TransparentEF):
prot_scheme_id_list_len = in_bytes[pos] # TODO maybe more than 1 byte prot_scheme_id_list_len = in_bytes[pos] # TODO maybe more than 1 byte
pos += 1 pos += 1
# decode Protection Scheme Identifier List data object # decode Protection Scheme Identifier List data object
prot_scheme_id_list = self._decode_prot_scheme_id_list(in_bytes[pos:pos+prot_scheme_id_list_len]) prot_scheme_id_list = self._decode_prot_scheme_id_list(
in_bytes[pos:pos+prot_scheme_id_list_len])
pos += prot_scheme_id_list_len pos += prot_scheme_id_list_len
# remaining data holds Home Network Public Key Data Object # remaining data holds Home Network Public Key Data Object
@@ -496,16 +509,19 @@ class EF_SUCI_Calc_Info(TransparentEF):
def _encode_bin(self, in_json): def _encode_bin(self, in_json):
return h2b(self._encode_hex(in_json)) return h2b(self._encode_hex(in_json))
class EF_LI(TransRecEF): class EF_LI(TransRecEF):
def __init__(self, fid='6f05', sfid=None, name='EF.LI', size={2, None}, rec_len=2, def __init__(self, fid='6f05', sfid=None, name='EF.LI', size={2, None}, rec_len=2,
desc='Language Indication'): desc='Language Indication'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
def _decode_record_bin(self, in_bin): def _decode_record_bin(self, in_bin):
if in_bin == b'\xff\xff': if in_bin == b'\xff\xff':
return None return None
else: else:
# officially this is 7-bit GSM alphabet with one padding bit in each byte # officially this is 7-bit GSM alphabet with one padding bit in each byte
return in_bin.decode('ascii') return in_bin.decode('ascii')
def _encode_record_bin(self, in_json): def _encode_record_bin(self, in_json):
if in_json == None: if in_json == None:
return b'\xff\xff' return b'\xff\xff'
@@ -513,13 +529,17 @@ class EF_LI(TransRecEF):
# officially this is 7-bit GSM alphabet with one padding bit in each byte # officially this is 7-bit GSM alphabet with one padding bit in each byte
return in_json.encode('ascii') return in_json.encode('ascii')
class EF_Keys(TransparentEF): class EF_Keys(TransparentEF):
def __init__(self, fid='6f08', sfid=0x08, name='EF.Keys', size={33, 33}, def __init__(self, fid='6f08', sfid=0x08, name='EF.Keys', size={33, 33},
desc='Ciphering and Integrity Keys'): desc='Ciphering and Integrity Keys'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
self._construct = Struct('ksi'/Int8ub, 'ck'/HexAdapter(Bytes(16)), 'ik'/HexAdapter(Bytes(16))) self._construct = Struct(
'ksi'/Int8ub, 'ck'/HexAdapter(Bytes(16)), 'ik'/HexAdapter(Bytes(16)))
# TS 31.102 Section 4.2.6 # TS 31.102 Section 4.2.6
class EF_HPPLMN(TransparentEF): class EF_HPPLMN(TransparentEF):
def __init__(self, fid='6f31', sfid=0x12, name='EF.HPPLMN', size={1, 1}, def __init__(self, fid='6f31', sfid=0x12, name='EF.HPPLMN', size={1, 1},
desc='Higher Priority PLMN search period'): desc='Higher Priority PLMN search period'):
@@ -527,18 +547,22 @@ class EF_HPPLMN(TransparentEF):
self._construct = Int8ub self._construct = Int8ub
# TS 31.102 Section 4.2.8 # TS 31.102 Section 4.2.8
class EF_UServiceTable(TransparentEF): class EF_UServiceTable(TransparentEF):
def __init__(self, fid, sfid, name, desc, size, table): def __init__(self, fid, sfid, name, desc, size, table):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, size=size)
self.table = table self.table = table
# add those commands to the general commands of a TransparentEF # add those commands to the general commands of a TransparentEF
self.shell_commands += [self.AddlShellCommands()] self.shell_commands += [self.AddlShellCommands()]
@staticmethod @staticmethod
def _bit_byte_offset_for_service(service: int) -> Tuple[int, int]: def _bit_byte_offset_for_service(service: int) -> Tuple[int, int]:
i = service - 1 i = service - 1
byte_offset = i//8 byte_offset = i//8
bit_offset = (i % 8) bit_offset = (i % 8)
return (byte_offset, bit_offset) return (byte_offset, bit_offset)
def _decode_bin(self, in_bin): def _decode_bin(self, in_bin):
ret = {} ret = {}
for i in range(0, len(in_bin)): for i in range(0, len(in_bin)):
@@ -551,25 +575,29 @@ class EF_UServiceTable(TransparentEF):
if service_nr in self.table: if service_nr in self.table:
ret[service_nr]['description'] = self.table[service_nr] ret[service_nr]['description'] = self.table[service_nr]
return ret return ret
def _encode_bin(self, in_json): def _encode_bin(self, in_json):
# compute the required binary size # compute the required binary size
bin_len = 0 bin_len = 0
for srv in in_json.keys(): for srv in in_json.keys():
service_nr = int(srv) service_nr = int(srv)
(byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(service_nr) (byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(
service_nr)
if byte_offset >= bin_len: if byte_offset >= bin_len:
bin_len = byte_offset+1 bin_len = byte_offset+1
# encode the actual data # encode the actual data
out = bytearray(b'\x00' * bin_len) out = bytearray(b'\x00' * bin_len)
for srv in in_json.keys(): for srv in in_json.keys():
service_nr = int(srv) service_nr = int(srv)
(byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(service_nr) (byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(
service_nr)
if in_json[srv]['activated'] == True: if in_json[srv]['activated'] == True:
bit = 1 bit = 1
else: else:
bit = 0 bit = 0
out[byte_offset] |= (bit) << bit_offset out[byte_offset] |= (bit) << bit_offset
return out return out
@with_default_category('File-Specific Commands') @with_default_category('File-Specific Commands')
class AddlShellCommands(CommandSet): class AddlShellCommands(CommandSet):
def __init__(self): def __init__(self):
@@ -584,14 +612,18 @@ class EF_UServiceTable(TransparentEF):
self._cmd.card.update_ust(int(arg), 0) self._cmd.card.update_ust(int(arg), 0)
# TS 31.103 Section 4.2.7 - *not* the same as DF.GSM/EF.ECC! # TS 31.103 Section 4.2.7 - *not* the same as DF.GSM/EF.ECC!
class EF_ECC(LinFixedEF): class EF_ECC(LinFixedEF):
cc_construct = Rpad(BcdAdapter(Rpad(Bytes(3))), pattern='f') cc_construct = Rpad(BcdAdapter(Rpad(Bytes(3))), pattern='f')
category_construct = FlagsEnum(Byte, police=1, ambulance=2, fire_brigade=3, marine_guard=4, category_construct = FlagsEnum(Byte, police=1, ambulance=2, fire_brigade=3, marine_guard=4,
mountain_rescue=5, manual_ecall=6, automatic_ecall=7) mountain_rescue=5, manual_ecall=6, automatic_ecall=7)
alpha_construct = GsmStringAdapter(Rpad(GreedyBytes)) alpha_construct = GsmStringAdapter(Rpad(GreedyBytes))
def __init__(self, fid='6fb7', sfid=0x01, name='EF.ECC', def __init__(self, fid='6fb7', sfid=0x01, name='EF.ECC',
desc='Emergency Call Codes'): desc='Emergency Call Codes'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={4, 20}) super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={4, 20})
def _decode_record_bin(self, in_bin): def _decode_record_bin(self, in_bin):
# mandatory parts # mandatory parts
code = in_bin[:3] code = in_bin[:3]
@@ -605,6 +637,7 @@ class EF_ECC(LinFixedEF):
alpha_id = in_bin[3:-1] alpha_id = in_bin[3:-1]
ret['alpha_id'] = parse_construct(EF_ECC.alpha_construct, alpha_id) ret['alpha_id'] = parse_construct(EF_ECC.alpha_construct, alpha_id)
return ret return ret
def _encode_record_bin(self, in_json): def _encode_record_bin(self, in_json):
if in_json is None: if in_json is None:
return b'\xff\xff\xff\xff' return b'\xff\xff\xff\xff'
@@ -622,6 +655,8 @@ class EF_LOCI(TransparentEF):
self._construct = Struct('tmsi'/HexAdapter(Bytes(4)), 'lai'/HexAdapter(Bytes(5)), 'rfu'/Int8ub, self._construct = Struct('tmsi'/HexAdapter(Bytes(4)), 'lai'/HexAdapter(Bytes(5)), 'rfu'/Int8ub,
'lu_status'/Int8ub) 'lu_status'/Int8ub)
# TS 31.102 Section 4.2.18 # TS 31.102 Section 4.2.18
class EF_AD(TransparentEF): class EF_AD(TransparentEF):
class OP_MODE(enum.IntEnum): class OP_MODE(enum.IntEnum):
normal = 0x00 normal = 0x00
@@ -645,6 +680,8 @@ class EF_AD(TransparentEF):
) )
# TS 31.102 Section 4.2.23 # TS 31.102 Section 4.2.23
class EF_PSLOCI(TransparentEF): class EF_PSLOCI(TransparentEF):
def __init__(self, fid='6f73', sfid=0x0c, name='EF.PSLOCI', desc='PS Location information', size={14, 14}): def __init__(self, fid='6f73', sfid=0x0c, name='EF.PSLOCI', desc='PS Location information', size={14, 14}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
@@ -652,6 +689,8 @@ class EF_PSLOCI(TransparentEF):
'rai'/HexAdapter(Bytes(6)), 'rau_status'/Int8ub) 'rai'/HexAdapter(Bytes(6)), 'rau_status'/Int8ub)
# TS 31.102 Section 4.2.33 # TS 31.102 Section 4.2.33
class EF_ICI(CyclicEF): class EF_ICI(CyclicEF):
def __init__(self, fid='6f80', sfid=0x14, name='EF.ICI', rec_len={28, 48}, def __init__(self, fid='6f80', sfid=0x14, name='EF.ICI', rec_len={28, 48},
desc='Incoming Call Information'): desc='Incoming Call Information'):
@@ -668,6 +707,8 @@ class EF_ICI(CyclicEF):
'link_to_phonebook'/Bytes(3)) 'link_to_phonebook'/Bytes(3))
# TS 31.102 Section 4.2.34 # TS 31.102 Section 4.2.34
class EF_OCI(CyclicEF): class EF_OCI(CyclicEF):
def __init__(self, fid='6f81', sfid=0x15, name='EF.OCI', rec_len={27, 47}, def __init__(self, fid='6f81', sfid=0x15, name='EF.OCI', rec_len={27, 47},
desc='Outgoing Call Information'): desc='Outgoing Call Information'):
@@ -683,6 +724,8 @@ class EF_OCI(CyclicEF):
'link_to_phonebook'/Bytes(3)) 'link_to_phonebook'/Bytes(3))
# TS 31.102 Section 4.2.35 # TS 31.102 Section 4.2.35
class EF_ICT(CyclicEF): class EF_ICT(CyclicEF):
def __init__(self, fid='6f82', sfid=None, name='EF.ICT', rec_len={3, 3}, def __init__(self, fid='6f82', sfid=None, name='EF.ICT', rec_len={3, 3},
desc='Incoming Call Timer'): desc='Incoming Call Timer'):
@@ -690,11 +733,16 @@ class EF_ICT(CyclicEF):
self._construct = Struct('accumulated_call_timer'/Int24ub) self._construct = Struct('accumulated_call_timer'/Int24ub)
# TS 31.102 Section 4.2.38 # TS 31.102 Section 4.2.38
class EF_CCP2(LinFixedEF): class EF_CCP2(LinFixedEF):
def __init__(self, fid='6f4f', sfid=0x16, name='EF.CCP2', desc='Capability Configuration Parameters 2'): def __init__(self, fid='6f4f', sfid=0x16, name='EF.CCP2', desc='Capability Configuration Parameters 2'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={15,None}) super().__init__(fid=fid, sfid=sfid,
name=name, desc=desc, rec_len={15, None})
# TS 31.102 Section 4.2.48 # TS 31.102 Section 4.2.48
class EF_ACL(TransparentEF): class EF_ACL(TransparentEF):
def __init__(self, fid='6f57', sfid=None, name='EF.ACL', size={32, None}, def __init__(self, fid='6f57', sfid=None, name='EF.ACL', size={32, None},
desc='Access Point Name Control List'): desc='Access Point Name Control List'):
@@ -702,6 +750,8 @@ class EF_ACL(TransparentEF):
self._construct = Struct('num_of_apns'/Int8ub, 'tlvs'/GreedyBytes) self._construct = Struct('num_of_apns'/Int8ub, 'tlvs'/GreedyBytes)
# TS 31.102 Section 4.2.51 # TS 31.102 Section 4.2.51
class EF_START_HFN(TransparentEF): class EF_START_HFN(TransparentEF):
def __init__(self, fid='6f5b', sfid=0x0f, name='EF.START-HFN', size={6, 6}, def __init__(self, fid='6f5b', sfid=0x0f, name='EF.START-HFN', size={6, 6},
desc='Initialisation values for Hyperframe number'): desc='Initialisation values for Hyperframe number'):
@@ -709,6 +759,8 @@ class EF_START_HFN(TransparentEF):
self._construct = Struct('start_cs'/Int24ub, 'start_ps'/Int24ub) self._construct = Struct('start_cs'/Int24ub, 'start_ps'/Int24ub)
# TS 31.102 Section 4.2.52 # TS 31.102 Section 4.2.52
class EF_THRESHOLD(TransparentEF): class EF_THRESHOLD(TransparentEF):
def __init__(self, fid='6f5c', sfid=0x10, name='EF.THRESHOLD', size={3, 3}, def __init__(self, fid='6f5c', sfid=0x10, name='EF.THRESHOLD', size={3, 3},
desc='Maximum value of START'): desc='Maximum value of START'):
@@ -716,6 +768,8 @@ class EF_THRESHOLD(TransparentEF):
self._construct = Struct('max_start'/Int24ub) self._construct = Struct('max_start'/Int24ub)
# TS 31.102 Section 4.2.77 # TS 31.102 Section 4.2.77
class EF_VGCSCA(TransRecEF): class EF_VGCSCA(TransRecEF):
def __init__(self, fid='6fd4', sfid=None, name='EF.VGCSCA', size={2, 100}, rec_len=2, def __init__(self, fid='6fd4', sfid=None, name='EF.VGCSCA', size={2, 100}, rec_len=2,
desc='Voice Group Call Service Ciphering Algorithm'): desc='Voice Group Call Service Ciphering Algorithm'):
@@ -723,6 +777,8 @@ class EF_VGCSCA(TransRecEF):
self._construct = Struct('alg_v_ki_1'/Int8ub, 'alg_v_ki_2'/Int8ub) self._construct = Struct('alg_v_ki_1'/Int8ub, 'alg_v_ki_2'/Int8ub)
# TS 31.102 Section 4.2.79 # TS 31.102 Section 4.2.79
class EF_GBABP(TransparentEF): class EF_GBABP(TransparentEF):
def __init__(self, fid='6fd6', sfid=None, name='EF.GBABP', size={3, 50}, def __init__(self, fid='6fd6', sfid=None, name='EF.GBABP', size={3, 50},
desc='GBA Bootstrapping parameters'): desc='GBA Bootstrapping parameters'):
@@ -730,42 +786,61 @@ class EF_GBABP(TransparentEF):
self._construct = Struct('rand'/LV, 'b_tid'/LV, 'key_lifetime'/LV) self._construct = Struct('rand'/LV, 'b_tid'/LV, 'key_lifetime'/LV)
# TS 31.102 Section 4.2.80 # TS 31.102 Section 4.2.80
class EF_MSK(LinFixedEF): class EF_MSK(LinFixedEF):
def __init__(self, fid='6fd7', sfid=None, name='EF.MSK', desc='MBMS Service Key List'): def __init__(self, fid='6fd7', sfid=None, name='EF.MSK', desc='MBMS Service Key List'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={20,None}) super().__init__(fid=fid, sfid=sfid,
name=name, desc=desc, rec_len={20, None})
msk_ts_constr = Struct('msk_id'/Int32ub, 'timestamp_counter'/Int32ub) msk_ts_constr = Struct('msk_id'/Int32ub, 'timestamp_counter'/Int32ub)
self._construct = Struct('key_domain_id'/Bytes(3), self._construct = Struct('key_domain_id'/Bytes(3),
'num_msk_id'/Int8ub, 'num_msk_id'/Int8ub,
'msk_ids'/msk_ts_constr[this.num_msk_id]) 'msk_ids'/msk_ts_constr[this.num_msk_id])
# TS 31.102 Section 4.2.81 # TS 31.102 Section 4.2.81
class EF_MUK(LinFixedEF): class EF_MUK(LinFixedEF):
class MUK_Idr(BER_TLV_IE, tag=0x80): class MUK_Idr(BER_TLV_IE, tag=0x80):
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class MUK_Idi(BER_TLV_IE, tag=0x82): class MUK_Idi(BER_TLV_IE, tag=0x82):
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class MUK_ID(BER_TLV_IE, tag=0xA0, nested=[MUK_Idr, MUK_Idi]): class MUK_ID(BER_TLV_IE, tag=0xA0, nested=[MUK_Idr, MUK_Idi]):
pass pass
class TimeStampCounter(BER_TLV_IE, tag=0x81): class TimeStampCounter(BER_TLV_IE, tag=0x81):
pass pass
class EF_MUK_Collection(TLV_IE_Collection, nested=[MUK_ID, TimeStampCounter]): class EF_MUK_Collection(TLV_IE_Collection, nested=[MUK_ID, TimeStampCounter]):
pass pass
def __init__(self, fid='6fd8', sfid=None, name='EF.MUK', desc='MBMS User Key'): def __init__(self, fid='6fd8', sfid=None, name='EF.MUK', desc='MBMS User Key'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={None,None}) super().__init__(fid=fid, sfid=sfid, name=name,
desc=desc, rec_len={None, None})
self._tlv = EF_MUK.EF_MUK_Collection self._tlv = EF_MUK.EF_MUK_Collection
# TS 31.102 Section 4.2.83 # TS 31.102 Section 4.2.83
class EF_GBANL(LinFixedEF): class EF_GBANL(LinFixedEF):
class NAF_ID(BER_TLV_IE, tag=0x80): class NAF_ID(BER_TLV_IE, tag=0x80):
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class B_TID(BER_TLV_IE, tag=0x81): class B_TID(BER_TLV_IE, tag=0x81):
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class EF_GBANL_Collection(BER_TLV_IE, nested=[NAF_ID, B_TID]): class EF_GBANL_Collection(BER_TLV_IE, nested=[NAF_ID, B_TID]):
pass pass
def __init__(self, fid='6fda', sfid=None, name='EF.GBANL', desc='GBA NAF List'): def __init__(self, fid='6fda', sfid=None, name='EF.GBANL', desc='GBA NAF List'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={None,None}) super().__init__(fid=fid, sfid=sfid, name=name,
desc=desc, rec_len={None, None})
self._tlv = EF_GBANL.EF_GBANL_Collection self._tlv = EF_GBANL.EF_GBANL_Collection
# TS 31.102 Section 4.2.85 # TS 31.102 Section 4.2.85
class EF_EHPLMNPI(TransparentEF): class EF_EHPLMNPI(TransparentEF):
def __init__(self, fid='6fdb', sfid=None, name='EF.EHPLMNPI', size={1, 1}, def __init__(self, fid='6fdb', sfid=None, name='EF.EHPLMNPI', size={1, 1},
desc='Equivalent HPLMN Presentation Indication'): desc='Equivalent HPLMN Presentation Indication'):
@@ -774,6 +849,8 @@ class EF_EHPLMNPI(TransparentEF):
Enum(Byte, no_preference=0, display_highest_prio_only=1, display_all=2)) Enum(Byte, no_preference=0, display_highest_prio_only=1, display_all=2))
# TS 31.102 Section 4.2.87 # TS 31.102 Section 4.2.87
class EF_NAFKCA(LinFixedEF): class EF_NAFKCA(LinFixedEF):
class NAF_KeyCentreAddress(BER_TLV_IE, tag=0x80): class NAF_KeyCentreAddress(BER_TLV_IE, tag=0x80):
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
@@ -783,23 +860,30 @@ class EF_NAFKCA(LinFixedEF):
self._tlv = EF_NAFKCA.NAF_KeyCentreAddress self._tlv = EF_NAFKCA.NAF_KeyCentreAddress
# TS 31.102 Section 4.2.90 # TS 31.102 Section 4.2.90
class EF_NCP_IP(LinFixedEF): class EF_NCP_IP(LinFixedEF):
class DataDestAddrRange(TLV_IE, tag=0x83): class DataDestAddrRange(TLV_IE, tag=0x83):
_construct = Struct('type_of_address'/Enum(Byte, IPv4=0x21, IPv6=0x56), _construct = Struct('type_of_address'/Enum(Byte, IPv4=0x21, IPv6=0x56),
'prefix_length'/Int8ub, 'prefix_length'/Int8ub,
'prefix'/HexAdapter(GreedyBytes)) 'prefix'/HexAdapter(GreedyBytes))
class AccessPointName(TLV_IE, tag=0x80): class AccessPointName(TLV_IE, tag=0x80):
# coded as per TS 23.003 # coded as per TS 23.003
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class Login(TLV_IE, tag=0x81): class Login(TLV_IE, tag=0x81):
# as per SMS DCS TS 23.038 # as per SMS DCS TS 23.038
_construct = GsmStringAdapter(GreedyBytes) _construct = GsmStringAdapter(GreedyBytes)
class Password(TLV_IE, tag=0x82): class Password(TLV_IE, tag=0x82):
# as per SMS DCS TS 23.038 # as per SMS DCS TS 23.038
_construct = GsmStringAdapter(GreedyBytes) _construct = GsmStringAdapter(GreedyBytes)
class BearerDescription(TLV_IE, tag=0x84): class BearerDescription(TLV_IE, tag=0x84):
# Bearer descriptionTLV DO as per TS 31.111 # Bearer descriptionTLV DO as per TS 31.111
pass pass
class EF_NCP_IP_Collection(TLV_IE_Collection, class EF_NCP_IP_Collection(TLV_IE_Collection,
nested=[AccessPointName, Login, Password, BearerDescription]): nested=[AccessPointName, Login, Password, BearerDescription]):
pass pass
@@ -809,26 +893,36 @@ class EF_NCP_IP(LinFixedEF):
self._tlv = EF_NCP_IP.EF_NCP_IP_Collection self._tlv = EF_NCP_IP.EF_NCP_IP_Collection
# TS 31.102 Section 4.2.91 # TS 31.102 Section 4.2.91
class EF_EPSLOCI(TransparentEF): class EF_EPSLOCI(TransparentEF):
def __init__(self, fid='6fe3', sfid=0x1e, name='EF.EPSLOCI', size={18, 18}, def __init__(self, fid='6fe3', sfid=0x1e, name='EF.EPSLOCI', size={18, 18},
desc='EPS Location Information'): desc='EPS Location Information'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
upd_status_constr = Enum(Byte, updated=0, not_updated=1, roaming_not_allowed=2) upd_status_constr = Enum(
Byte, updated=0, not_updated=1, roaming_not_allowed=2)
self._construct = Struct('guti'/Bytes(12), 'last_visited_registered_tai'/Bytes(5), self._construct = Struct('guti'/Bytes(12), 'last_visited_registered_tai'/Bytes(5),
'eps_update_status'/upd_status_constr) 'eps_update_status'/upd_status_constr)
# TS 31.102 Section 4.2.92 # TS 31.102 Section 4.2.92
class EF_EPSNSC(LinFixedEF): class EF_EPSNSC(LinFixedEF):
class KSI_ASME(BER_TLV_IE, tag=0x80): class KSI_ASME(BER_TLV_IE, tag=0x80):
_construct = Int8ub _construct = Int8ub
class K_ASME(BER_TLV_IE, tag=0x81): class K_ASME(BER_TLV_IE, tag=0x81):
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class UplinkNASCount(BER_TLV_IE, tag=0x82): class UplinkNASCount(BER_TLV_IE, tag=0x82):
_construct = Int32ub _construct = Int32ub
class DownlinkNASCount(BER_TLV_IE, tag=0x83): class DownlinkNASCount(BER_TLV_IE, tag=0x83):
_construct = Int32ub _construct = Int32ub
class IDofNASAlgorithms(BER_TLV_IE, tag=0x84): class IDofNASAlgorithms(BER_TLV_IE, tag=0x84):
_construct = HexAdapter(GreedyBytes) _construct = HexAdapter(GreedyBytes)
class EPS_NAS_Security_Context(BER_TLV_IE, tag=0xa0, class EPS_NAS_Security_Context(BER_TLV_IE, tag=0xa0,
nested=[KSI_ASME, K_ASME, UplinkNASCount, DownlinkNASCount, nested=[KSI_ASME, K_ASME, UplinkNASCount, DownlinkNASCount,
IDofNASAlgorithms]): IDofNASAlgorithms]):
@@ -839,13 +933,18 @@ class EF_EPSNSC(LinFixedEF):
self._tlv = EF_EPSNSC.EPS_NAS_Security_Context self._tlv = EF_EPSNSC.EPS_NAS_Security_Context
# TS 31.102 Section 4.2.96 # TS 31.102 Section 4.2.96
class EF_PWS(TransparentEF): class EF_PWS(TransparentEF):
def __init__(self, fid='6fec', sfid=None, name='EF.PWS', desc='Public Warning System', size={1, 1}): def __init__(self, fid='6fec', sfid=None, name='EF.PWS', desc='Public Warning System', size={1, 1}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
pws_config = FlagsEnum(Byte, ignore_pws_in_hplmn_and_equivalent=1, ignore_pws_in_vplmn=2) pws_config = FlagsEnum(
Byte, ignore_pws_in_hplmn_and_equivalent=1, ignore_pws_in_vplmn=2)
self._construct = Struct('pws_configuration'/pws_config) self._construct = Struct('pws_configuration'/pws_config)
# TS 31.102 Section 4.2.101 # TS 31.102 Section 4.2.101
class EF_IPS(CyclicEF): class EF_IPS(CyclicEF):
def __init__(self, fid='6ff1', sfid=None, name='EF.IPS', rec_len={4, 4}, def __init__(self, fid='6ff1', sfid=None, name='EF.IPS', rec_len={4, 4},
desc='IMEI(SV) Pairing Status'): desc='IMEI(SV) Pairing Status'):
@@ -854,6 +953,8 @@ class EF_IPS(CyclicEF):
'link_to_ef_ipd'/Int8ub, 'rfu'/Byte) 'link_to_ef_ipd'/Int8ub, 'rfu'/Byte)
# TS 31.102 Section 4.2.103 # TS 31.102 Section 4.2.103
class EF_ePDGId(TransparentEF): class EF_ePDGId(TransparentEF):
class ePDGId(BER_TLV_IE, tag=0x80, nested=[]): class ePDGId(BER_TLV_IE, tag=0x80, nested=[]):
_construct = Struct('type_of_ePDG_address'/Enum(Byte, FQDN=0, IPv4=1, IPv6=2), _construct = Struct('type_of_ePDG_address'/Enum(Byte, FQDN=0, IPv4=1, IPv6=2),
@@ -861,11 +962,14 @@ class EF_ePDGId(TransparentEF):
{'FQDN': GreedyString("utf8"), {'FQDN': GreedyString("utf8"),
'IPv4': HexAdapter(GreedyBytes), 'IPv4': HexAdapter(GreedyBytes),
'IPv6': HexAdapter(GreedyBytes)})) 'IPv6': HexAdapter(GreedyBytes)}))
def __init__(self, fid='6ff3', sfid=None, name='EF.eDPDGId', desc='Home ePDG Identifier'): def __init__(self, fid='6ff3', sfid=None, name='EF.eDPDGId', desc='Home ePDG Identifier'):
super().__init__(fid, sfid=sfid, name=name, desc=desc) super().__init__(fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_ePDGId.ePDGId self._tlv = EF_ePDGId.ePDGId
# TS 31.102 Section 4.2.106 # TS 31.102 Section 4.2.106
class EF_FromPreferred(TransparentEF): class EF_FromPreferred(TransparentEF):
def __init__(self, fid='6ff7', sfid=None, name='EF.FromPreferred', size={1, 1}, def __init__(self, fid='6ff7', sfid=None, name='EF.FromPreferred', size={1, 1},
desc='From Preferred'): desc='From Preferred'):
@@ -877,15 +981,20 @@ class EF_FromPreferred(TransparentEF):
###################################################################### ######################################################################
# TS 31.102 Section 4.4.11.2 # TS 31.102 Section 4.4.11.2
class EF_5GS3GPPLOCI(TransparentEF): class EF_5GS3GPPLOCI(TransparentEF):
def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size={20, 20}, def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size={20, 20},
desc='5S 3GP location information'): desc='5S 3GP location information'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
upd_status_constr = Enum(Byte, updated=0, not_updated=1, roaming_not_allowed=2) upd_status_constr = Enum(
Byte, updated=0, not_updated=1, roaming_not_allowed=2)
self._construct = Struct('5g_guti'/Bytes(13), 'last_visited_registered_tai_in_5gs'/Bytes(6), self._construct = Struct('5g_guti'/Bytes(13), 'last_visited_registered_tai_in_5gs'/Bytes(6),
'5gs_update_status'/upd_status_constr) '5gs_update_status'/upd_status_constr)
# TS 31.102 Section 4.4.11.7 # TS 31.102 Section 4.4.11.7
class EF_UAC_AIC(TransparentEF): class EF_UAC_AIC(TransparentEF):
def __init__(self, fid='4f06', sfid=0x06, name='EF.UAC_AIC', size={4, 4}, def __init__(self, fid='4f06', sfid=0x06, name='EF.UAC_AIC', size={4, 4},
desc='UAC Access Identities Configuration'): desc='UAC Access Identities Configuration'):
@@ -895,22 +1004,30 @@ class EF_UAC_AIC(TransparentEF):
self._construct = Struct('uac_access_id_config'/cfg_constr) self._construct = Struct('uac_access_id_config'/cfg_constr)
# TS 31.102 Section 4.4.11.9 # TS 31.102 Section 4.4.11.9
class EF_OPL5G(LinFixedEF): class EF_OPL5G(LinFixedEF):
def __init__(self, fid='6f08', sfid=0x08, name='EF.OPL5G', desc='5GS Operator PLMN List'): def __init__(self, fid='6f08', sfid=0x08, name='EF.OPL5G', desc='5GS Operator PLMN List'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={10,None}) super().__init__(fid=fid, sfid=sfid,
name=name, desc=desc, rec_len={10, None})
self._construct = Struct('tai'/Bytes(9), 'pnn_record_id'/Int8ub) self._construct = Struct('tai'/Bytes(9), 'pnn_record_id'/Int8ub)
# TS 31.102 Section 4.4.11.10 # TS 31.102 Section 4.4.11.10
class EF_SUPI_NAI(TransparentEF): class EF_SUPI_NAI(TransparentEF):
class NetworkSpecificIdentifier(TLV_IE, tag=0x80): class NetworkSpecificIdentifier(TLV_IE, tag=0x80):
# RFC 7542 encoded as UTF-8 string # RFC 7542 encoded as UTF-8 string
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
class GlobalLineIdentifier(TLV_IE, tag=0x81): class GlobalLineIdentifier(TLV_IE, tag=0x81):
# TS 23.003 clause 28.16.2 # TS 23.003 clause 28.16.2
pass pass
class GlobalCableIdentifier(TLV_IE, tag=0x82): class GlobalCableIdentifier(TLV_IE, tag=0x82):
# TS 23.003 clause 28.15.2 # TS 23.003 clause 28.15.2
pass pass
class NAI_TLV_Collection(TLV_IE_Collection, class NAI_TLV_Collection(TLV_IE_Collection,
nested=[NetworkSpecificIdentifier, GlobalLineIdentifier, GlobalCableIdentifier]): nested=[NetworkSpecificIdentifier, GlobalLineIdentifier, GlobalCableIdentifier]):
pass pass
@@ -919,6 +1036,7 @@ class EF_SUPI_NAI(TransparentEF):
super().__init__(fid, sfid=sfid, name=name, desc=desc) super().__init__(fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_SUPI_NAI.NAI_TLV_Collection self._tlv = EF_SUPI_NAI.NAI_TLV_Collection
class EF_TN3GPPSNN(TransparentEF): class EF_TN3GPPSNN(TransparentEF):
class ServingNetworkName(BER_TLV_IE, tag=0x80): class ServingNetworkName(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
@@ -928,25 +1046,39 @@ class EF_TN3GPPSNN(TransparentEF):
self._tlv = EF_TN3GPPSNN.ServingNetworkName self._tlv = EF_TN3GPPSNN.ServingNetworkName
# TS 31.102 Section 4.4.5 # TS 31.102 Section 4.4.5
class DF_WLAN(CardDF): class DF_WLAN(CardDF):
def __init__(self, fid='5f40', name='DF.WLAN', desc='Files for WLAN purpose'): def __init__(self, fid='5f40', name='DF.WLAN', desc='Files for WLAN purpose'):
super().__init__(fid=fid, name=name, desc=desc) super().__init__(fid=fid, name=name, desc=desc)
files = [ files = [
TransparentEF('4f41', 0x01, 'EF.Pseudo', 'Pseudonym'), TransparentEF('4f41', 0x01, 'EF.Pseudo', 'Pseudonym'),
TransparentEF('4f42', 0x02, 'EF.UPLMNWLAN', 'User controlled PLMN selector for I-WLAN Access'), TransparentEF('4f42', 0x02, 'EF.UPLMNWLAN',
TransparentEF('4f43', 0x03, 'EF.OPLMNWLAN', 'Operator controlled PLMN selector for I-WLAN Access'), 'User controlled PLMN selector for I-WLAN Access'),
LinFixedEF('4f44', 0x04, 'EF.UWSIDL', 'User controlled WLAN Specific Identifier List'), TransparentEF('4f43', 0x03, 'EF.OPLMNWLAN',
LinFixedEF('4f45', 0x05, 'EF.OWSIDL', 'Operator controlled WLAN Specific Identifier List'), 'Operator controlled PLMN selector for I-WLAN Access'),
TransparentEF('4f46', 0x06, 'EF.WRI', 'WLAN Reauthentication Identity'), LinFixedEF('4f44', 0x04, 'EF.UWSIDL',
LinFixedEF('4f47', 0x07, 'EF.HWSIDL', 'Home I-WLAN Specific Identifier List'), 'User controlled WLAN Specific Identifier List'),
TransparentEF('4f48', 0x08, 'EF.WEHPLMNPI', 'I-WLAN Equivalent HPLMN Presentation Indication'), LinFixedEF('4f45', 0x05, 'EF.OWSIDL',
TransparentEF('4f49', 0x09, 'EF.WHPI', 'I-WLAN HPLMN Priority Indication'), 'Operator controlled WLAN Specific Identifier List'),
TransparentEF('4f4a', 0x0a, 'EF.WLRPLMN', 'I-WLAN Last Registered PLMN'), TransparentEF('4f46', 0x06, 'EF.WRI',
TransparentEF('4f4b', 0x0b, 'EF.HPLMNDAI', 'HPLMN Direct Access Indicator'), 'WLAN Reauthentication Identity'),
LinFixedEF('4f47', 0x07, 'EF.HWSIDL',
'Home I-WLAN Specific Identifier List'),
TransparentEF('4f48', 0x08, 'EF.WEHPLMNPI',
'I-WLAN Equivalent HPLMN Presentation Indication'),
TransparentEF('4f49', 0x09, 'EF.WHPI',
'I-WLAN HPLMN Priority Indication'),
TransparentEF('4f4a', 0x0a, 'EF.WLRPLMN',
'I-WLAN Last Registered PLMN'),
TransparentEF('4f4b', 0x0b, 'EF.HPLMNDAI',
'HPLMN Direct Access Indicator'),
] ]
self.add_files(files) self.add_files(files)
# TS 31.102 Section 4.4.6 # TS 31.102 Section 4.4.6
class DF_HNB(CardDF): class DF_HNB(CardDF):
def __init__(self, fid='5f50', name='DF.HNB', desc='Files for HomeNodeB purpose'): def __init__(self, fid='5f50', name='DF.HNB', desc='Files for HomeNodeB purpose'):
super().__init__(fid=fid, name=name, desc=desc) super().__init__(fid=fid, name=name, desc=desc)
@@ -961,47 +1093,65 @@ class DF_HNB(CardDF):
self.add_files(files) self.add_files(files)
# TS 31.102 Section 4.4.8 # TS 31.102 Section 4.4.8
class DF_ProSe(CardDF): class DF_ProSe(CardDF):
def __init__(self, fid='5f90', name='DF.ProSe', desc='Files for ProSe purpose'): def __init__(self, fid='5f90', name='DF.ProSe', desc='Files for ProSe purpose'):
super().__init__(fid=fid, name=name, desc=desc) super().__init__(fid=fid, name=name, desc=desc)
files = [ files = [
LinFixedEF('4f01', 0x01, 'EF.PROSE_MON', 'ProSe Monitoring Parameters'), LinFixedEF('4f01', 0x01, 'EF.PROSE_MON',
LinFixedEF('4f02', 0x02, 'EF.PROSE_ANN', 'ProSe Announcing Parameters'), 'ProSe Monitoring Parameters'),
LinFixedEF('4f02', 0x02, 'EF.PROSE_ANN',
'ProSe Announcing Parameters'),
LinFixedEF('4f03', 0x03, 'EF.PROSEFUNC', 'HPLMN ProSe Function'), LinFixedEF('4f03', 0x03, 'EF.PROSEFUNC', 'HPLMN ProSe Function'),
TransparentEF('4f04', 0x04, 'EF.PROSE_RADIO_COM', 'ProSe Direct Communication Radio Parameters'), TransparentEF('4f04', 0x04, 'EF.PROSE_RADIO_COM',
TransparentEF('4f05', 0x05, 'EF.PROSE_RADIO_MON', 'ProSe Direct Discovery Monitoring Radio Parameters'), 'ProSe Direct Communication Radio Parameters'),
TransparentEF('4f06', 0x06, 'EF.PROSE_RADIO_ANN', 'ProSe Direct Discovery Announcing Radio Parameters'), TransparentEF('4f05', 0x05, 'EF.PROSE_RADIO_MON',
LinFixedEF('4f07', 0x07, 'EF.PROSE_POLICY', 'ProSe Policy Parameters'), 'ProSe Direct Discovery Monitoring Radio Parameters'),
TransparentEF('4f06', 0x06, 'EF.PROSE_RADIO_ANN',
'ProSe Direct Discovery Announcing Radio Parameters'),
LinFixedEF('4f07', 0x07, 'EF.PROSE_POLICY',
'ProSe Policy Parameters'),
LinFixedEF('4f08', 0x08, 'EF.PROSE_PLMN', 'ProSe PLMN Parameters'), LinFixedEF('4f08', 0x08, 'EF.PROSE_PLMN', 'ProSe PLMN Parameters'),
TransparentEF('4f09', 0x09, 'EF.PROSE_GC', 'ProSe Group Counter'), TransparentEF('4f09', 0x09, 'EF.PROSE_GC', 'ProSe Group Counter'),
TransparentEF('4f10', 0x10, 'EF.PST', 'ProSe Service Table'), TransparentEF('4f10', 0x10, 'EF.PST', 'ProSe Service Table'),
TransparentEF('4f11', 0x11, 'EF.UIRC', 'ProSe UsageInformationReportingConfiguration'), TransparentEF('4f11', 0x11, 'EF.UIRC',
LinFixedEF('4f12', 0x12, 'EF.PROSE_GM_DISCOVERY', 'ProSe Group Member Discovery Parameters'), 'ProSe UsageInformationReportingConfiguration'),
LinFixedEF('4f13', 0x13, 'EF.PROSE_RELAY', 'ProSe Relay Parameters'), LinFixedEF('4f12', 0x12, 'EF.PROSE_GM_DISCOVERY',
TransparentEF('4f14', 0x14, 'EF.PROSE_RELAY_DISCOVERY', 'ProSe Relay Discovery Parameters'), 'ProSe Group Member Discovery Parameters'),
LinFixedEF('4f13', 0x13, 'EF.PROSE_RELAY',
'ProSe Relay Parameters'),
TransparentEF('4f14', 0x14, 'EF.PROSE_RELAY_DISCOVERY',
'ProSe Relay Discovery Parameters'),
] ]
self.add_files(files) self.add_files(files)
class DF_USIM_5GS(CardDF): class DF_USIM_5GS(CardDF):
def __init__(self, fid='5FC0', name='DF.5GS', desc='5GS related files'): def __init__(self, fid='5FC0', name='DF.5GS', desc='5GS related files'):
super().__init__(fid=fid, name=name, desc=desc) super().__init__(fid=fid, name=name, desc=desc)
files = [ files = [
# I'm looking at 31.102 R16.6 # I'm looking at 31.102 R16.6
EF_5GS3GPPLOCI(), EF_5GS3GPPLOCI(),
EF_5GS3GPPLOCI('4f02', 0x02, 'EF.5GSN3GPPLOCI', '5GS non-3GPP location information'), EF_5GS3GPPLOCI('4f02', 0x02, 'EF.5GSN3GPPLOCI',
'5GS non-3GPP location information'),
EF_5GS3GPPNSC(), EF_5GS3GPPNSC(),
EF_5GS3GPPNSC('4f04', 0x04, 'EF.5GSN3GPPNSC', '5GS non-3GPP Access NAS Security Context'), EF_5GS3GPPNSC('4f04', 0x04, 'EF.5GSN3GPPNSC',
'5GS non-3GPP Access NAS Security Context'),
EF_5GAUTHKEYS(), EF_5GAUTHKEYS(),
EF_UAC_AIC(), EF_UAC_AIC(),
EF_SUCI_Calc_Info(), EF_SUCI_Calc_Info(),
EF_OPL5G(), EF_OPL5G(),
EF_SUPI_NAI(), EF_SUPI_NAI(),
TransparentEF('4F0A', 0x0a, 'EF.Routing_Indicator', 'Routing Indicator', size={4,4}), TransparentEF('4F0A', 0x0a, 'EF.Routing_Indicator',
TransparentEF('4F0B', 0x0b, 'EF.URSP', 'UE Route Selector Policies per PLMN'), 'Routing Indicator', size={4, 4}),
TransparentEF('4F0B', 0x0b, 'EF.URSP',
'UE Route Selector Policies per PLMN'),
EF_TN3GPPSNN(), EF_TN3GPPSNN(),
] ]
self.add_files(files) self.add_files(files)
class ADF_USIM(CardADF): class ADF_USIM(CardADF):
def __init__(self, aid='a0000000871002', name='ADF.USIM', fid=None, sfid=None, def __init__(self, aid='a0000000871002', name='ADF.USIM', fid=None, sfid=None,
desc='USIM Application'): desc='USIM Application'):
@@ -1013,20 +1163,25 @@ class ADF_USIM(CardADF):
EF_LI(sfid=0x02), EF_LI(sfid=0x02),
EF_IMSI(sfid=0x07), EF_IMSI(sfid=0x07),
EF_Keys(), EF_Keys(),
EF_Keys('6f09', 0x09, 'EF.KeysPS', desc='Ciphering and Integrity Keys for PS domain'), EF_Keys('6f09', 0x09, 'EF.KeysPS',
desc='Ciphering and Integrity Keys for PS domain'),
EF_xPLMNwAcT('6f60', 0x0a, 'EF.PLMNwAcT', EF_xPLMNwAcT('6f60', 0x0a, 'EF.PLMNwAcT',
'User controlled PLMN Selector with Access Technology'), 'User controlled PLMN Selector with Access Technology'),
EF_HPPLMN(), EF_HPPLMN(),
EF_ACMmax(), EF_ACMmax(),
EF_UServiceTable('6f38', 0x04, 'EF.UST', 'USIM Service Table', size={1,17}, table=EF_UST_map), EF_UServiceTable('6f38', 0x04, 'EF.UST', 'USIM Service Table', size={
CyclicEF('6f39', None, 'EF.ACM', 'Accumulated call meter', rec_len={3,3}), 1, 17}, table=EF_UST_map),
CyclicEF('6f39', None, 'EF.ACM',
'Accumulated call meter', rec_len={3, 3}),
TransparentEF('6f3e', None, 'EF.GID1', 'Group Identifier Level 1'), TransparentEF('6f3e', None, 'EF.GID1', 'Group Identifier Level 1'),
TransparentEF('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'), TransparentEF('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'),
EF_SPN(), EF_SPN(),
TransparentEF('6f41', None, 'EF.PUCT', 'Price per unit and currency table', size={5,5}), TransparentEF('6f41', None, 'EF.PUCT',
'Price per unit and currency table', size={5, 5}),
EF_CBMI(), EF_CBMI(),
EF_ACC(sfid=0x06), EF_ACC(sfid=0x06),
EF_PLMNsel('6f7b', 0x0d, 'EF.FPLMN', 'Forbidden PLMNs', size={12,None}), EF_PLMNsel('6f7b', 0x0d, 'EF.FPLMN',
'Forbidden PLMNs', size={12, None}),
EF_LOCI(), EF_LOCI(),
EF_AD(), EF_AD(),
EF_CBMID(sfid=0x0e), EF_CBMID(sfid=0x0e),
@@ -1054,7 +1209,8 @@ class ADF_USIM(CardADF):
EF_ADN('6f4d', None, 'EF.BDN', 'Barred Dialling Numbers'), EF_ADN('6f4d', None, 'EF.BDN', 'Barred Dialling Numbers'),
EF_EXT('6f55', None, 'EF.EXT4', 'Extension4 (BDN/SSC)'), EF_EXT('6f55', None, 'EF.EXT4', 'Extension4 (BDN/SSC)'),
EF_CMI(), EF_CMI(),
EF_UServiceTable('6f56', 0x05, 'EF.EST', 'Enabled Services Table', size={1,None}, table=EF_EST_map), EF_UServiceTable('6f56', 0x05, 'EF.EST', 'Enabled Services Table', size={
1, None}, table=EF_EST_map),
EF_ACL(), EF_ACL(),
EF_DCK(), EF_DCK(),
EF_CNL(), EF_CNL(),
@@ -1069,9 +1225,11 @@ class ADF_USIM(CardADF):
EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'), EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'),
EF_MBI(), EF_MBI(),
EF_MWIS(), EF_MWIS(),
EF_ADN('6fcb', None, 'EF.CFIS', 'Call Forwarding Indication Status'), EF_ADN('6fcb', None, 'EF.CFIS',
'Call Forwarding Indication Status'),
EF_EXT('6fcc', None, 'EF.EXT7', 'Extension7 (CFIS)'), EF_EXT('6fcc', None, 'EF.EXT7', 'Extension7 (CFIS)'),
TransparentEF('6fcd', None, 'EF.SPDI', 'Service Provider Display Information'), TransparentEF('6fcd', None, 'EF.SPDI',
'Service Provider Display Information'),
EF_MMSN(), EF_MMSN(),
EF_EXT('6fcf', None, 'EF.EXT8', 'Extension8 (MMSN)'), EF_EXT('6fcf', None, 'EF.EXT8', 'Extension8 (MMSN)'),
EF_MMSICP(), EF_MMSICP(),
@@ -1081,28 +1239,37 @@ class ADF_USIM(CardADF):
EF_VGCS(), EF_VGCS(),
EF_VGCSS(), EF_VGCSS(),
EF_VGCS('6fb3', None, 'EF.VBS', 'Voice Broadcast Service'), EF_VGCS('6fb3', None, 'EF.VBS', 'Voice Broadcast Service'),
EF_VGCSS('6fb4', None, 'EF.VBSS', 'Voice Broadcast Service Status'), EF_VGCSS('6fb4', None, 'EF.VBSS',
'Voice Broadcast Service Status'),
EF_VGCSCA(), EF_VGCSCA(),
EF_VGCSCA('6fd5', None, 'EF.VBCSCA', 'Voice Broadcast Service Ciphering Algorithm'), EF_VGCSCA('6fd5', None, 'EF.VBCSCA',
'Voice Broadcast Service Ciphering Algorithm'),
EF_GBABP(), EF_GBABP(),
EF_MSK(), EF_MSK(),
EF_MUK(), EF_MUK(),
EF_GBANL(), EF_GBANL(),
EF_PLMNsel('6fd9', 0x1d, 'EF.EHPLMN', 'Equivalent HPLMN', size={12,None}), EF_PLMNsel('6fd9', 0x1d, 'EF.EHPLMN',
'Equivalent HPLMN', size={12, None}),
EF_EHPLMNPI(), EF_EHPLMNPI(),
EF_NAFKCA(), EF_NAFKCA(),
TransparentEF('6fde', None, 'EF.SPNI', 'Service Provider Name Icon'), TransparentEF('6fde', None, 'EF.SPNI',
'Service Provider Name Icon'),
LinFixedEF('6fdf', None, 'EF.PNNI', 'PLMN Network Name Icon'), LinFixedEF('6fdf', None, 'EF.PNNI', 'PLMN Network Name Icon'),
EF_NCP_IP(), EF_NCP_IP(),
EF_EPSLOCI('6fe3', 0x1e, 'EF.EPSLOCI', 'EPS location information'), EF_EPSLOCI('6fe3', 0x1e, 'EF.EPSLOCI', 'EPS location information'),
EF_EPSNSC(), EF_EPSNSC(),
TransparentEF('6fe6', None, 'EF.UFC', 'USAT Facility Control', size={1,16}), TransparentEF('6fe6', None, 'EF.UFC',
TransparentEF('6fe8', None, 'EF.NASCONFIG', 'Non Access Stratum Configuration'), 'USAT Facility Control', size={1, 16}),
TransparentEF('6fe8', None, 'EF.NASCONFIG',
'Non Access Stratum Configuration'),
# UICC IARI (only in cards that have no ISIM) # UICC IARI (only in cards that have no ISIM)
EF_PWS(), EF_PWS(),
LinFixedEF('6fed', None, 'EF.FDNURI', 'Fixed Dialling Numbers URI'), LinFixedEF('6fed', None, 'EF.FDNURI',
LinFixedEF('6fee', None, 'EF.BDNURI', 'Barred Dialling Numbers URI'), 'Fixed Dialling Numbers URI'),
LinFixedEF('6fef', None, 'EF.SDNURI', 'Service Dialling Numbers URI'), LinFixedEF('6fee', None, 'EF.BDNURI',
'Barred Dialling Numbers URI'),
LinFixedEF('6fef', None, 'EF.SDNURI',
'Service Dialling Numbers URI'),
EF_IPS(), EF_IPS(),
EF_ePDGId(), EF_ePDGId(),
# FIXME: from EF_ePDGSelection onwards # FIXME: from EF_ePDGSelection onwards
@@ -1131,6 +1298,7 @@ class ADF_USIM(CardADF):
authenticate_parser.add_argument('rand', help='Random challenge') authenticate_parser.add_argument('rand', help='Random challenge')
authenticate_parser.add_argument('autn', help='Authentication Nonce') authenticate_parser.add_argument('autn', help='Authentication Nonce')
#authenticate_parser.add_argument('--context', help='Authentication context', default='3G') #authenticate_parser.add_argument('--context', help='Authentication context', default='3G')
@cmd2.with_argparser(authenticate_parser) @cmd2.with_argparser(authenticate_parser)
def do_authenticate(self, opts): def do_authenticate(self, opts):
"""Perform Authentication and Key Agreement (AKA).""" """Perform Authentication and Key Agreement (AKA)."""
@@ -1151,7 +1319,8 @@ class ADF_USIM(CardADF):
"""Send an ENVELOPE command to the card.""" """Send an ENVELOPE command to the card."""
tpdu_ie = SMS_TPDU() tpdu_ie = SMS_TPDU()
tpdu_ie.from_bytes(h2b(arg)) tpdu_ie.from_bytes(h2b(arg))
dev_ids = DeviceIdentities(decoded={'source_dev_id':'network','dest_dev_id':'uicc'}) dev_ids = DeviceIdentities(
decoded={'source_dev_id': 'network', 'dest_dev_id': 'uicc'})
sms_dl = SMSPPDownload(children=[dev_ids, tpdu_ie]) sms_dl = SMSPPDownload(children=[dev_ids, tpdu_ie])
(data, sw) = self._cmd.card._scc.envelope(b2h(sms_dl.to_tlv())) (data, sw) = self._cmd.card._scc.envelope(b2h(sms_dl.to_tlv()))
self._cmd.poutput('SW: %s, data: %s' % (sw, data)) self._cmd.poutput('SW: %s, data: %s' % (sw, data))
@@ -1168,6 +1337,7 @@ sw_usim = {
} }
} }
class CardApplicationUSIM(CardApplication): class CardApplicationUSIM(CardApplication):
def __init__(self): def __init__(self):
super().__init__('USIM', adf=ADF_USIM(), sw=sw_usim) super().__init__('USIM', adf=ADF_USIM(), sw=sw_usim)

View File

@@ -78,83 +78,114 @@ EF_ISIM_ADF_map = {
} }
# TS 31.103 Section 4.2.2 # TS 31.103 Section 4.2.2
class EF_IMPI(TransparentEF): class EF_IMPI(TransparentEF):
class nai(BER_TLV_IE, tag=0x80): class nai(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6f02', sfid=0x02, name='EF.IMPI', desc='IMS private user identity'): def __init__(self, fid='6f02', sfid=0x02, name='EF.IMPI', desc='IMS private user identity'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_IMPI.nai self._tlv = EF_IMPI.nai
# TS 31.103 Section 4.2.3 # TS 31.103 Section 4.2.3
class EF_DOMAIN(TransparentEF): class EF_DOMAIN(TransparentEF):
class domain(BER_TLV_IE, tag=0x80): class domain(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6f05', sfid=0x05, name='EF.DOMAIN', desc='Home Network Domain Name'): def __init__(self, fid='6f05', sfid=0x05, name='EF.DOMAIN', desc='Home Network Domain Name'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_DOMAIN.domain self._tlv = EF_DOMAIN.domain
# TS 31.103 Section 4.2.4 # TS 31.103 Section 4.2.4
class EF_IMPU(LinFixedEF): class EF_IMPU(LinFixedEF):
class impu(BER_TLV_IE, tag=0x80): class impu(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6f04', sfid=0x04, name='EF.IMPU', desc='IMS public user identity'): def __init__(self, fid='6f04', sfid=0x04, name='EF.IMPU', desc='IMS public user identity'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_IMPU.impu self._tlv = EF_IMPU.impu
# TS 31.103 Section 4.2.8 # TS 31.103 Section 4.2.8
class EF_PCSCF(LinFixedEF): class EF_PCSCF(LinFixedEF):
def __init__(self, fid='6f09', sfid=None, name='EF.P-CSCF', desc='P-CSCF Address'): def __init__(self, fid='6f09', sfid=None, name='EF.P-CSCF', desc='P-CSCF Address'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
def _decode_record_hex(self, raw_hex): def _decode_record_hex(self, raw_hex):
addr, addr_type = dec_addr_tlv(raw_hex) addr, addr_type = dec_addr_tlv(raw_hex)
return {"addr": addr, "addr_type": addr_type} return {"addr": addr, "addr_type": addr_type}
def _encode_record_hex(self, json_in): def _encode_record_hex(self, json_in):
addr = json_in['addr'] addr = json_in['addr']
addr_type = json_in['addr_type'] addr_type = json_in['addr_type']
return enc_addr_tlv(addr, addr_type) return enc_addr_tlv(addr, addr_type)
# TS 31.103 Section 4.2.9 # TS 31.103 Section 4.2.9
class EF_GBABP(TransparentEF): class EF_GBABP(TransparentEF):
def __init__(self, fid='6fd5', sfid=None, name='EF.GBABP', desc='GBA Bootstrapping'): def __init__(self, fid='6fd5', sfid=None, name='EF.GBABP', desc='GBA Bootstrapping'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.10 # TS 31.103 Section 4.2.10
class EF_GBANL(LinFixedEF): class EF_GBANL(LinFixedEF):
def __init__(self, fid='6fd7', sfid=None, name='EF.GBANL', desc='GBA NAF List'): def __init__(self, fid='6fd7', sfid=None, name='EF.GBANL', desc='GBA NAF List'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.11 # TS 31.103 Section 4.2.11
class EF_NAFKCA(LinFixedEF): class EF_NAFKCA(LinFixedEF):
def __init__(self, fid='6fdd', sfid=None, name='EF.NAFKCA', desc='NAF Key Centre Address'): def __init__(self, fid='6fdd', sfid=None, name='EF.NAFKCA', desc='NAF Key Centre Address'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.16 # TS 31.103 Section 4.2.16
class EF_UICCIARI(LinFixedEF): class EF_UICCIARI(LinFixedEF):
class iari(BER_TLV_IE, tag=0x80): class iari(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6fe7', sfid=None, name='EF.UICCIARI', desc='UICC IARI'): def __init__(self, fid='6fe7', sfid=None, name='EF.UICCIARI', desc='UICC IARI'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_UICCIARI.iari self._tlv = EF_UICCIARI.iari
# TS 31.103 Section 4.2.18 # TS 31.103 Section 4.2.18
class EF_IMSConfigData(BerTlvEF): class EF_IMSConfigData(BerTlvEF):
def __init__(self, fid='6ff8', sfid=None, name='EF.IMSConfigData', desc='IMS Configuration Data'): def __init__(self, fid='6ff8', sfid=None, name='EF.IMSConfigData', desc='IMS Configuration Data'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.19 # TS 31.103 Section 4.2.19
class EF_XCAPConfigData(BerTlvEF): class EF_XCAPConfigData(BerTlvEF):
def __init__(self, fid='6ffc', sfid=None, name='EF.XCAPConfigData', desc='XCAP Configuration Data'): def __init__(self, fid='6ffc', sfid=None, name='EF.XCAPConfigData', desc='XCAP Configuration Data'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.20 # TS 31.103 Section 4.2.20
class EF_WebRTCURI(TransparentEF): class EF_WebRTCURI(TransparentEF):
class uri(BER_TLV_IE, tag=0x80): class uri(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8") _construct = GreedyString("utf8")
def __init__(self, fid='6ffa', sfid=None, name='EF.WebRTCURI', desc='WebRTC URI'): def __init__(self, fid='6ffa', sfid=None, name='EF.WebRTCURI', desc='WebRTC URI'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_WebRTCURI.uri self._tlv = EF_WebRTCURI.uri
# TS 31.103 Section 4.2.21 # TS 31.103 Section 4.2.21
class EF_MuDMiDConfigData(BerTlvEF): class EF_MuDMiDConfigData(BerTlvEF):
def __init__(self, fid='6ffe', sfid=None, name='EF.MuDMiDConfigData', def __init__(self, fid='6ffe', sfid=None, name='EF.MuDMiDConfigData',
desc='MuD and MiD Configuration Data'): desc='MuD and MiD Configuration Data'):
@@ -172,7 +203,8 @@ class ADF_ISIM(CardADF):
EF_IMPU(), EF_IMPU(),
EF_AD(), EF_AD(),
EF_ARR('6f06', 0x06), EF_ARR('6f06', 0x06),
EF_UServiceTable('6f07', 0x07, 'EF.IST', 'ISIM Service Table', {1,None}, EF_IST_map), EF_UServiceTable('6f07', 0x07, 'EF.IST',
'ISIM Service Table', {1, None}, EF_IST_map),
EF_PCSCF(), EF_PCSCF(),
EF_GBABP(), EF_GBABP(),
EF_GBANL(), EF_GBANL(),
@@ -195,6 +227,7 @@ class ADF_ISIM(CardADF):
def decode_select_response(self, data_hex): def decode_select_response(self, data_hex):
return pySim.ts_102_221.CardProfileUICC.decode_select_response(data_hex) return pySim.ts_102_221.CardProfileUICC.decode_select_response(data_hex)
# TS 31.103 Section 7.1 # TS 31.103 Section 7.1
sw_isim = { sw_isim = {
'Security management': { 'Security management': {
@@ -203,6 +236,7 @@ sw_isim = {
} }
} }
class CardApplicationISIM(CardApplication): class CardApplicationISIM(CardApplication):
def __init__(self): def __init__(self):
super().__init__('ISIM', adf=ADF_ISIM(), sw=sw_isim) super().__init__('ISIM', adf=ADF_ISIM(), sw=sw_isim)

View File

@@ -29,6 +29,17 @@ order to describe the files specified in the relevant ETSI + 3GPP specifications
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from pySim.profile import match_sim
from pySim.profile import CardProfile
from pySim.filesystem import *
import enum
from pySim.construct import *
from construct import Optional as COptional
from construct import *
from struct import pack, unpack
from typing import Tuple
from pySim.tlv import *
from pySim.utils import *
MF_num = '3F00' MF_num = '3F00'
DF_num = { DF_num = {
@@ -323,27 +334,18 @@ EF_SST_map = {
59: 'MMS User Connectivity Parameters', 59: 'MMS User Connectivity Parameters',
} }
from pySim.utils import *
from pySim.tlv import *
from typing import Tuple
from struct import pack, unpack
from construct import *
from construct import Optional as COptional
from pySim.construct import *
import enum
from pySim.filesystem import *
from pySim.profile import CardProfile
from pySim.profile import match_sim
###################################################################### ######################################################################
# DF.TELECOM # DF.TELECOM
###################################################################### ######################################################################
# TS 51.011 Section 10.5.1 # TS 51.011 Section 10.5.1
class EF_ADN(LinFixedEF): class EF_ADN(LinFixedEF):
def __init__(self, fid='6f3a', sfid=None, name='EF.ADN', desc='Abbreviated Dialing Numbers'): def __init__(self, fid='6f3a', sfid=None, name='EF.ADN', desc='Abbreviated Dialing Numbers'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={14, 30}) super().__init__(fid, sfid=sfid, name=name,
desc=desc, rec_len={14, 30})
def _decode_record_bin(self, raw_bin_data): def _decode_record_bin(self, raw_bin_data):
alpha_id_len = len(raw_bin_data) - 14 alpha_id_len = len(raw_bin_data) - 14
alpha_id = raw_bin_data[:alpha_id_len] alpha_id = raw_bin_data[:alpha_id_len]
@@ -352,9 +354,13 @@ class EF_ADN(LinFixedEF):
'dialing_nr': u[2], 'cap_conf_id': u[3], 'ext1_record_id': u[4]} 'dialing_nr': u[2], 'cap_conf_id': u[3], 'ext1_record_id': u[4]}
# TS 51.011 Section 10.5.5 # TS 51.011 Section 10.5.5
class EF_SMS(LinFixedEF): class EF_SMS(LinFixedEF):
def __init__(self, fid='6f3c', sfid=None, name='EF.SMS', desc='Short messages'): def __init__(self, fid='6f3c', sfid=None, name='EF.SMS', desc='Short messages'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={176,176}) super().__init__(fid, sfid=sfid, name=name,
desc=desc, rec_len={176, 176})
def _decode_record_bin(self, raw_bin_data): def _decode_record_bin(self, raw_bin_data):
def decode_status(status): def decode_status(status):
if status & 0x01 == 0x00: if status & 0x01 == 0x00:
@@ -384,51 +390,72 @@ class EF_SMS(LinFixedEF):
# TS 51.011 Section 10.5.5 # TS 51.011 Section 10.5.5
class EF_MSISDN(LinFixedEF): class EF_MSISDN(LinFixedEF):
def __init__(self, fid='6f40', sfid=None, name='EF.MSISDN', desc='MSISDN'): def __init__(self, fid='6f40', sfid=None, name='EF.MSISDN', desc='MSISDN'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={15, 34}) super().__init__(fid, sfid=sfid, name=name,
desc=desc, rec_len={15, 34})
def _decode_record_hex(self, raw_hex_data): def _decode_record_hex(self, raw_hex_data):
return {'msisdn': dec_msisdn(raw_hex_data)} return {'msisdn': dec_msisdn(raw_hex_data)}
def _encode_record_hex(self, abstract): def _encode_record_hex(self, abstract):
msisdn = abstract['msisdn'] msisdn = abstract['msisdn']
if type(msisdn) == str: if type(msisdn) == str:
encoded_msisdn = enc_msisdn(msisdn) encoded_msisdn = enc_msisdn(msisdn)
else: else:
encoded_msisdn = enc_msisdn(msisdn[2], msisdn[0], msisdn[1]) encoded_msisdn = enc_msisdn(msisdn[2], msisdn[0], msisdn[1])
alpha_identifier = (list(self.rec_len)[0] - len(encoded_msisdn) // 2) * "ff" alpha_identifier = (list(self.rec_len)[
0] - len(encoded_msisdn) // 2) * "ff"
return alpha_identifier + encoded_msisdn return alpha_identifier + encoded_msisdn
# TS 51.011 Section 10.5.6 # TS 51.011 Section 10.5.6
class EF_SMSP(LinFixedEF): class EF_SMSP(LinFixedEF):
def __init__(self, fid='6f42', sfid=None, name='EF.SMSP', desc='Short message service parameters'): def __init__(self, fid='6f42', sfid=None, name='EF.SMSP', desc='Short message service parameters'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={28, None}) super().__init__(fid, sfid=sfid, name=name,
desc=desc, rec_len={28, None})
# TS 51.011 Section 10.5.7 # TS 51.011 Section 10.5.7
class EF_SMSS(TransparentEF): class EF_SMSS(TransparentEF):
class MemCapAdapter(Adapter): class MemCapAdapter(Adapter):
def _decode(self, obj, context, path): def _decode(self, obj, context, path):
return False if obj & 1 else True return False if obj & 1 else True
def _encode(self, obj, context, path): def _encode(self, obj, context, path):
return 0 if obj else 1 return 0 if obj else 1
def __init__(self, fid='6f43', sfid=None, name='EF.SMSS', desc='SMS status', size={2, 8}): def __init__(self, fid='6f43', sfid=None, name='EF.SMSS', desc='SMS status', size={2, 8}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
self._construct = Struct('last_used_tpmr'/Int8ub, 'memory_capacity_exceeded'/self.MemCapAdapter(Int8ub)) self._construct = Struct(
'last_used_tpmr'/Int8ub, 'memory_capacity_exceeded'/self.MemCapAdapter(Int8ub))
# TS 51.011 Section 10.5.8 # TS 51.011 Section 10.5.8
class EF_SMSR(LinFixedEF): class EF_SMSR(LinFixedEF):
def __init__(self, fid='6f47', sfid=None, name='EF.SMSR', desc='SMS status reports', rec_len={30, 30}): def __init__(self, fid='6f47', sfid=None, name='EF.SMSR', desc='SMS status reports', rec_len={30, 30}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
self._construct = Struct('sms_record_id'/Int8ub, 'sms_status_report'/HexAdapter(Bytes(29))) self._construct = Struct(
'sms_record_id'/Int8ub, 'sms_status_report'/HexAdapter(Bytes(29)))
class EF_EXT(LinFixedEF): class EF_EXT(LinFixedEF):
def __init__(self, fid, sfid=None, name='EF.EXT', desc='Extension', rec_len={13, 13}): def __init__(self, fid, sfid=None, name='EF.EXT', desc='Extension', rec_len={13, 13}):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
self._construct = Struct('record_type'/Int8ub, 'extension_data'/HexAdapter(Bytes(11)), 'identifier'/Int8ub) self._construct = Struct(
'record_type'/Int8ub, 'extension_data'/HexAdapter(Bytes(11)), 'identifier'/Int8ub)
# TS 51.011 Section 10.5.16 # TS 51.011 Section 10.5.16
class EF_CMI(LinFixedEF): class EF_CMI(LinFixedEF):
def __init__(self, fid='6f58', sfid=None, name='EF.CMI', rec_len={2, 21}, def __init__(self, fid='6f58', sfid=None, name='EF.CMI', rec_len={2, 21},
desc='Comparison Method Information'): desc='Comparison Method Information'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
self._construct = Struct('alpha_id'/Bytes(this._.total_len-1), 'comparison_method_id'/Int8ub) self._construct = Struct(
'alpha_id'/Bytes(this._.total_len-1), 'comparison_method_id'/Int8ub)
class DF_TELECOM(CardDF): class DF_TELECOM(CardDF):
def __init__(self, fid='7f10', name='DF.TELECOM', desc=None): def __init__(self, fid='7f10', name='DF.TELECOM', desc=None):
@@ -437,8 +464,10 @@ class DF_TELECOM(CardDF):
EF_ADN(), EF_ADN(),
EF_ADN(fid='6f3b', name='EF.FDN', desc='Fixed dialling numbers'), EF_ADN(fid='6f3b', name='EF.FDN', desc='Fixed dialling numbers'),
EF_SMS(), EF_SMS(),
LinFixedEF(fid='6f3d', name='EF.CCP', desc='Capability Configuration Parameters', rec_len={14,14}), LinFixedEF(fid='6f3d', name='EF.CCP',
LinFixedEF(fid='6f4f', name='EF.ECCP', desc='Extended Capability Configuration Parameters', rec_len={15,32}), desc='Capability Configuration Parameters', rec_len={14, 14}),
LinFixedEF(fid='6f4f', name='EF.ECCP',
desc='Extended Capability Configuration Parameters', rec_len={15, 32}),
EF_MSISDN(), EF_MSISDN(),
EF_SMSP(), EF_SMSP(),
EF_SMSS(), EF_SMSS(),
@@ -458,25 +487,34 @@ class DF_TELECOM(CardDF):
###################################################################### ######################################################################
# TS 51.011 Section 10.3.1 # TS 51.011 Section 10.3.1
class EF_LP(TransRecEF): class EF_LP(TransRecEF):
def __init__(self, fid='6f05', sfid=None, name='EF.LP', size={1, None}, rec_len=1, def __init__(self, fid='6f05', sfid=None, name='EF.LP', size={1, None}, rec_len=1,
desc='Language Preference'): desc='Language Preference'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
def _decode_record_bin(self, in_bin): def _decode_record_bin(self, in_bin):
return b2h(in_bin) return b2h(in_bin)
def _encode_record_bin(self, in_json): def _encode_record_bin(self, in_json):
return h2b(in_json) return h2b(in_json)
# TS 51.011 Section 10.3.2 # TS 51.011 Section 10.3.2
class EF_IMSI(TransparentEF): class EF_IMSI(TransparentEF):
def __init__(self, fid='6f07', sfid=None, name='EF.IMSI', desc='IMSI', size={9, 9}): def __init__(self, fid='6f07', sfid=None, name='EF.IMSI', desc='IMSI', size={9, 9}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
# add those commands to the general commands of a TransparentEF # add those commands to the general commands of a TransparentEF
self.shell_commands += [self.AddlShellCommands(self)] self.shell_commands += [self.AddlShellCommands(self)]
def _decode_hex(self, raw_hex): def _decode_hex(self, raw_hex):
return {'imsi': dec_imsi(raw_hex)} return {'imsi': dec_imsi(raw_hex)}
def _encode_hex(self, abstract): def _encode_hex(self, abstract):
return enc_imsi(abstract['imsi']) return enc_imsi(abstract['imsi'])
@with_default_category('File-Specific Commands') @with_default_category('File-Specific Commands')
class AddlShellCommands(CommandSet): class AddlShellCommands(CommandSet):
def __init__(self, ef: TransparentEF): def __init__(self, ef: TransparentEF):
@@ -491,9 +529,11 @@ class EF_IMSI(TransparentEF):
if sw == '9000' and len(data['imsi'])-len(plmn) == 10: if sw == '9000' and len(data['imsi'])-len(plmn) == 10:
imsi = data['imsi'] imsi = data['imsi']
msin = imsi[len(plmn):] msin = imsi[len(plmn):]
(data, sw) = self._cmd.rs.update_binary_dec({'imsi': plmn+msin}) (data, sw) = self._cmd.rs.update_binary_dec(
{'imsi': plmn+msin})
if sw == '9000' and data: if sw == '9000' and data:
self._cmd.poutput_json(self._cmd.rs.selected_file.decode_hex(data)) self._cmd.poutput_json(
self._cmd.rs.selected_file.decode_hex(data))
else: else:
raise ValueError("PLMN length does not match IMSI length") raise ValueError("PLMN length does not match IMSI length")
else: else:
@@ -505,11 +545,13 @@ class EF_PLMNsel(TransRecEF):
def __init__(self, fid='6f30', sfid=None, name='EF.PLMNsel', desc='PLMN selector', def __init__(self, fid='6f30', sfid=None, name='EF.PLMNsel', desc='PLMN selector',
size={24, None}, rec_len=3): size={24, None}, rec_len=3):
super().__init__(fid, name=name, sfid=sfid, desc=desc, size=size, rec_len=rec_len) super().__init__(fid, name=name, sfid=sfid, desc=desc, size=size, rec_len=rec_len)
def _decode_record_hex(self, in_hex): def _decode_record_hex(self, in_hex):
if in_hex[:6] == "ffffff": if in_hex[:6] == "ffffff":
return None return None
else: else:
return dec_plmn(in_hex) return dec_plmn(in_hex)
def _encode_record_hex(self, in_json): def _encode_record_hex(self, in_json):
if in_json == None: if in_json == None:
return "ffffff" return "ffffff"
@@ -517,6 +559,8 @@ class EF_PLMNsel(TransRecEF):
return enc_plmn(in_json['mcc'], in_json['mnc']) return enc_plmn(in_json['mcc'], in_json['mnc'])
# TS 51.011 Section 10.3.6 # TS 51.011 Section 10.3.6
class EF_ACMmax(TransparentEF): class EF_ACMmax(TransparentEF):
def __init__(self, fid='6f37', sfid=None, name='EF.ACMmax', size={3, 3}, def __init__(self, fid='6f37', sfid=None, name='EF.ACMmax', size={3, 3},
desc='ACM maximum value'): desc='ACM maximum value'):
@@ -524,16 +568,20 @@ class EF_ACMmax(TransparentEF):
self._construct = Struct('acm_max'/Int24ub) self._construct = Struct('acm_max'/Int24ub)
# TS 51.011 Section 10.3.7 # TS 51.011 Section 10.3.7
class EF_ServiceTable(TransparentEF): class EF_ServiceTable(TransparentEF):
def __init__(self, fid, sfid, name, desc, size, table): def __init__(self, fid, sfid, name, desc, size, table):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
self.table = table self.table = table
@staticmethod @staticmethod
def _bit_byte_offset_for_service(service: int) -> Tuple[int, int]: def _bit_byte_offset_for_service(service: int) -> Tuple[int, int]:
i = service - 1 i = service - 1
byte_offset = i//4 byte_offset = i//4
bit_offset = (i % 4) * 2 bit_offset = (i % 4) * 2
return (byte_offset, bit_offset) return (byte_offset, bit_offset)
def _decode_bin(self, raw_bin): def _decode_bin(self, raw_bin):
ret = {} ret = {}
for i in range(0, len(raw_bin)*4): for i in range(0, len(raw_bin)*4):
@@ -547,19 +595,22 @@ class EF_ServiceTable(TransparentEF):
'activated': True if bits & 2 else False, 'activated': True if bits & 2 else False,
} }
return ret return ret
def _encode_bin(self, in_json): def _encode_bin(self, in_json):
# compute the required binary size # compute the required binary size
bin_len = 0 bin_len = 0
for srv in in_json.keys(): for srv in in_json.keys():
service_nr = int(srv) service_nr = int(srv)
(byte_offset, bit_offset) = EF_ServiceTable._bit_byte_offset_for_service(service_nr) (byte_offset, bit_offset) = EF_ServiceTable._bit_byte_offset_for_service(
service_nr)
if byte_offset >= bin_len: if byte_offset >= bin_len:
bin_len = byte_offset+1 bin_len = byte_offset+1
# encode the actual data # encode the actual data
out = bytearray(b'\x00' * bin_len) out = bytearray(b'\x00' * bin_len)
for srv in in_json.keys(): for srv in in_json.keys():
service_nr = int(srv) service_nr = int(srv)
(byte_offset, bit_offset) = EF_ServiceTable._bit_byte_offset_for_service(service_nr) (byte_offset, bit_offset) = EF_ServiceTable._bit_byte_offset_for_service(
service_nr)
bits = 0 bits = 0
if in_json[srv]['allocated'] == True: if in_json[srv]['allocated'] == True:
bits |= 1 bits |= 1
@@ -569,6 +620,8 @@ class EF_ServiceTable(TransparentEF):
return out return out
# TS 51.011 Section 10.3.11 # TS 51.011 Section 10.3.11
class EF_SPN(TransparentEF): class EF_SPN(TransparentEF):
def __init__(self, fid='6f46', sfid=None, name='EF.SPN', desc='Service Provider Name', size={17, 17}): def __init__(self, fid='6f46', sfid=None, name='EF.SPN', desc='Service Provider Name', size={17, 17}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
@@ -582,6 +635,8 @@ class EF_SPN(TransparentEF):
) )
# TS 51.011 Section 10.3.13 # TS 51.011 Section 10.3.13
class EF_CBMI(TransRecEF): class EF_CBMI(TransRecEF):
def __init__(self, fid='6f45', sfid=None, name='EF.CBMI', size={2, None}, rec_len=2, def __init__(self, fid='6f45', sfid=None, name='EF.CBMI', size={2, None}, rec_len=2,
desc='Cell Broadcast message identifier selection'): desc='Cell Broadcast message identifier selection'):
@@ -589,15 +644,21 @@ class EF_CBMI(TransRecEF):
self._construct = GreedyRange(Int16ub) self._construct = GreedyRange(Int16ub)
# TS 51.011 Section 10.3.15 # TS 51.011 Section 10.3.15
class EF_ACC(TransparentEF): class EF_ACC(TransparentEF):
def __init__(self, fid='6f78', sfid=None, name='EF.ACC', desc='Access Control Class', size={2, 2}): def __init__(self, fid='6f78', sfid=None, name='EF.ACC', desc='Access Control Class', size={2, 2}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
def _decode_bin(self, raw_bin): def _decode_bin(self, raw_bin):
return {'acc': unpack('!H', raw_bin)[0]} return {'acc': unpack('!H', raw_bin)[0]}
def _encode_bin(self, abstract): def _encode_bin(self, abstract):
return pack('!H', abstract['acc']) return pack('!H', abstract['acc'])
# TS 51.011 Section 10.3.16 # TS 51.011 Section 10.3.16
class EF_LOCI(TransparentEF): class EF_LOCI(TransparentEF):
def __init__(self, fid='6f7e', sfid=None, name='EF.LOCI', desc='Location Information', size={11, 11}): def __init__(self, fid='6f7e', sfid=None, name='EF.LOCI', desc='Location Information', size={11, 11}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
@@ -606,6 +667,8 @@ class EF_LOCI(TransparentEF):
location_area_not_allowed=3)) location_area_not_allowed=3))
# TS 51.011 Section 10.3.18 # TS 51.011 Section 10.3.18
class EF_AD(TransparentEF): class EF_AD(TransparentEF):
class OP_MODE(enum.IntEnum): class OP_MODE(enum.IntEnum):
normal = 0x00 normal = 0x00
@@ -637,6 +700,8 @@ class EF_AD(TransparentEF):
) )
# TS 51.011 Section 10.3.20 / 10.3.22 # TS 51.011 Section 10.3.20 / 10.3.22
class EF_VGCS(TransRecEF): class EF_VGCS(TransRecEF):
def __init__(self, fid='6fb1', sfid=None, name='EF.VGCS', size={4, 200}, rec_len=4, def __init__(self, fid='6fb1', sfid=None, name='EF.VGCS', size={4, 200}, rec_len=4,
desc='Voice Group Call Service'): desc='Voice Group Call Service'):
@@ -644,29 +709,41 @@ class EF_VGCS(TransRecEF):
self._construct = BcdAdapter(Bytes(4)) self._construct = BcdAdapter(Bytes(4))
# TS 51.011 Section 10.3.21 / 10.3.23 # TS 51.011 Section 10.3.21 / 10.3.23
class EF_VGCSS(TransparentEF): class EF_VGCSS(TransparentEF):
def __init__(self, fid='6fb2', sfid=None, name='EF.VGCSS', size={7, 7}, def __init__(self, fid='6fb2', sfid=None, name='EF.VGCSS', size={7, 7},
desc='Voice Group Call Service Status'): desc='Voice Group Call Service Status'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
self._construct = BitStruct('flags'/Bit[50], Padding(6, pattern=b'\xff')) self._construct = BitStruct(
'flags'/Bit[50], Padding(6, pattern=b'\xff'))
# TS 51.011 Section 10.3.24 # TS 51.011 Section 10.3.24
class EF_eMLPP(TransparentEF): class EF_eMLPP(TransparentEF):
def __init__(self, fid='6fb5', sfid=None, name='EF.eMLPP', size={2, 2}, def __init__(self, fid='6fb5', sfid=None, name='EF.eMLPP', size={2, 2},
desc='enhanced Multi Level Pre-emption and Priority'): desc='enhanced Multi Level Pre-emption and Priority'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
FlagsConstruct = FlagsEnum(Byte, A=1, B=2, zero=4, one=8, two=16, three=32, four=64) FlagsConstruct = FlagsEnum(
self._construct = Struct('levels'/FlagsConstruct, 'fast_call_setup_cond'/FlagsConstruct) Byte, A=1, B=2, zero=4, one=8, two=16, three=32, four=64)
self._construct = Struct(
'levels'/FlagsConstruct, 'fast_call_setup_cond'/FlagsConstruct)
# TS 51.011 Section 10.3.25 # TS 51.011 Section 10.3.25
class EF_AAeM(TransparentEF): class EF_AAeM(TransparentEF):
def __init__(self, fid='6fb6', sfid=None, name='EF.AAeM', size={1, 1}, def __init__(self, fid='6fb6', sfid=None, name='EF.AAeM', size={1, 1},
desc='Automatic Answer for eMLPP Service'): desc='Automatic Answer for eMLPP Service'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
FlagsConstruct = FlagsEnum(Byte, A=1, B=2, zero=4, one=8, two=16, three=32, four=64) FlagsConstruct = FlagsEnum(
Byte, A=1, B=2, zero=4, one=8, two=16, three=32, four=64)
self._construct = Struct('auto_answer_prio_levels'/FlagsConstruct) self._construct = Struct('auto_answer_prio_levels'/FlagsConstruct)
# TS 51.011 Section 10.3.26 # TS 51.011 Section 10.3.26
class EF_CBMID(EF_CBMI): class EF_CBMID(EF_CBMI):
def __init__(self, fid='6f48', sfid=None, name='EF.CBMID', size={2, None}, rec_len=2, def __init__(self, fid='6f48', sfid=None, name='EF.CBMID', size={2, None}, rec_len=2,
desc='Cell Broadcast Message Identifier for Data Download'): desc='Cell Broadcast Message Identifier for Data Download'):
@@ -674,6 +751,8 @@ class EF_CBMID(EF_CBMI):
self._construct = GreedyRange(Int16ub) self._construct = GreedyRange(Int16ub)
# TS 51.011 Section 10.3.27 # TS 51.011 Section 10.3.27
class EF_ECC(TransRecEF): class EF_ECC(TransRecEF):
def __init__(self, fid='6fb7', sfid=None, name='EF.ECC', size={3, 15}, rec_len=3, def __init__(self, fid='6fb7', sfid=None, name='EF.ECC', size={3, 15}, rec_len=3,
desc='Emergency Call Codes'): desc='Emergency Call Codes'):
@@ -681,6 +760,8 @@ class EF_ECC(TransRecEF):
self._construct = GreedyRange(BcdAdapter(Bytes(3))) self._construct = GreedyRange(BcdAdapter(Bytes(3)))
# TS 51.011 Section 10.3.28 # TS 51.011 Section 10.3.28
class EF_CBMIR(TransRecEF): class EF_CBMIR(TransRecEF):
def __init__(self, fid='6f50', sfid=None, name='EF.CBMIR', size={4, None}, rec_len=4, def __init__(self, fid='6f50', sfid=None, name='EF.CBMIR', size={4, None}, rec_len=4,
desc='Cell Broadcast message identifier range selection'): desc='Cell Broadcast message identifier range selection'):
@@ -688,6 +769,8 @@ class EF_CBMIR(TransRecEF):
self._construct = GreedyRange(Struct('lower'/Int16ub, 'upper'/Int16ub)) self._construct = GreedyRange(Struct('lower'/Int16ub, 'upper'/Int16ub))
# TS 51.011 Section 10.3.29 # TS 51.011 Section 10.3.29
class EF_DCK(TransparentEF): class EF_DCK(TransparentEF):
def __init__(self, fid='6f2c', sfid=None, name='EF.DCK', size={16, 16}, def __init__(self, fid='6f2c', sfid=None, name='EF.DCK', size={16, 16},
desc='Depersonalisation Control Keys'): desc='Depersonalisation Control Keys'):
@@ -697,10 +780,13 @@ class EF_DCK(TransparentEF):
'service_provider'/BcdAdapter(Bytes(4)), 'service_provider'/BcdAdapter(Bytes(4)),
'corporate'/BcdAdapter(Bytes(4))) 'corporate'/BcdAdapter(Bytes(4)))
# TS 51.011 Section 10.3.30 # TS 51.011 Section 10.3.30
class EF_CNL(TransRecEF): class EF_CNL(TransRecEF):
def __init__(self, fid='6f32', sfid=None, name='EF.CNL', size={6, None}, rec_len=6, def __init__(self, fid='6f32', sfid=None, name='EF.CNL', size={6, None}, rec_len=6,
desc='Co-operative Network List'): desc='Co-operative Network List'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
def _decode_record_hex(self, in_hex): def _decode_record_hex(self, in_hex):
(in_plmn, sub, svp, corp) = unpack('!3sBBB', h2b(in_hex)) (in_plmn, sub, svp, corp) = unpack('!3sBBB', h2b(in_hex))
res = dec_plmn(b2h(in_plmn)) res = dec_plmn(b2h(in_plmn))
@@ -708,6 +794,7 @@ class EF_CNL(TransRecEF):
res['service_provider_id'] = svp res['service_provider_id'] = svp
res['corporate_id'] = corp res['corporate_id'] = corp
return res return res
def _encode_record_hex(self, in_json): def _encode_record_hex(self, in_json):
plmn = enc_plmn(in_json['mcc'], in_json['mnc']) plmn = enc_plmn(in_json['mcc'], in_json['mnc'])
return b2h(pack('!3sBBB', return b2h(pack('!3sBBB',
@@ -717,19 +804,26 @@ class EF_CNL(TransRecEF):
in_json['corporate_id'])) in_json['corporate_id']))
# TS 51.011 Section 10.3.31 # TS 51.011 Section 10.3.31
class EF_NIA(LinFixedEF): class EF_NIA(LinFixedEF):
def __init__(self, fid='6f51', sfid=None, name='EF.NIA', rec_len={1, 32}, def __init__(self, fid='6f51', sfid=None, name='EF.NIA', rec_len={1, 32},
desc='Network\'s Indication of Alerting'): desc='Network\'s Indication of Alerting'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
self._construct = Struct('alerting_category'/Int8ub, 'category'/GreedyBytes) self._construct = Struct(
'alerting_category'/Int8ub, 'category'/GreedyBytes)
# TS 51.011 Section 10.3.32 # TS 51.011 Section 10.3.32
class EF_Kc(TransparentEF): class EF_Kc(TransparentEF):
def __init__(self, fid='6f20', sfid=None, name='EF.Kc', desc='Ciphering key Kc', size={9, 9}): def __init__(self, fid='6f20', sfid=None, name='EF.Kc', desc='Ciphering key Kc', size={9, 9}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
self._construct = Struct('kc'/HexAdapter(Bytes(8)), 'cksn'/Int8ub) self._construct = Struct('kc'/HexAdapter(Bytes(8)), 'cksn'/Int8ub)
# TS 51.011 Section 10.3.33 # TS 51.011 Section 10.3.33
class EF_LOCIGPRS(TransparentEF): class EF_LOCIGPRS(TransparentEF):
def __init__(self, fid='6f53', sfid=None, name='EF.LOCIGPRS', desc='GPRS Location Information', size={14, 14}): def __init__(self, fid='6f53', sfid=None, name='EF.LOCIGPRS', desc='GPRS Location Information', size={14, 14}):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
@@ -738,14 +832,18 @@ class EF_LOCIGPRS(TransparentEF):
routing_area_not_allowed=3)) routing_area_not_allowed=3))
# TS 51.011 Section 10.3.35..37 # TS 51.011 Section 10.3.35..37
class EF_xPLMNwAcT(TransRecEF): class EF_xPLMNwAcT(TransRecEF):
def __init__(self, fid, sfid=None, name=None, desc=None, size={40, None}, rec_len=5): def __init__(self, fid, sfid=None, name=None, desc=None, size={40, None}, rec_len=5):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
def _decode_record_hex(self, in_hex): def _decode_record_hex(self, in_hex):
if in_hex[:6] == "ffffff": if in_hex[:6] == "ffffff":
return None return None
else: else:
return dec_xplmn_w_act(in_hex) return dec_xplmn_w_act(in_hex)
def _encode_record_hex(self, in_json): def _encode_record_hex(self, in_json):
if in_json == None: if in_json == None:
return "ffffff0000" return "ffffff0000"
@@ -753,6 +851,7 @@ class EF_xPLMNwAcT(TransRecEF):
hplmn = enc_plmn(in_json['mcc'], in_json['mnc']) hplmn = enc_plmn(in_json['mcc'], in_json['mnc'])
act = self.enc_act(in_json['act']) act = self.enc_act(in_json['act'])
return hplmn + act return hplmn + act
@staticmethod @staticmethod
def enc_act(in_list): def enc_act(in_list):
u16 = 0 u16 = 0
@@ -784,6 +883,8 @@ class EF_xPLMNwAcT(TransRecEF):
return '%04X' % (u16) return '%04X' % (u16)
# TS 51.011 Section 10.3.38 # TS 51.011 Section 10.3.38
class EF_CPBCCH(TransRecEF): class EF_CPBCCH(TransRecEF):
def __init__(self, fid='6f63', sfid=None, name='EF.CPBCCH', size={2, 14}, rec_len=2, def __init__(self, fid='6f63', sfid=None, name='EF.CPBCCH', size={2, 14}, rec_len=2,
desc='CPBCCH Information'): desc='CPBCCH Information'):
@@ -791,33 +892,45 @@ class EF_CPBCCH(TransRecEF):
self._construct = Struct('cpbcch'/Int16ub) self._construct = Struct('cpbcch'/Int16ub)
# TS 51.011 Section 10.3.39 # TS 51.011 Section 10.3.39
class EF_InvScan(TransparentEF): class EF_InvScan(TransparentEF):
def __init__(self, fid='6f64', sfid=None, name='EF.InvScan', size={1, 1}, def __init__(self, fid='6f64', sfid=None, name='EF.InvScan', size={1, 1},
desc='IOnvestigation Scan'): desc='IOnvestigation Scan'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
self._construct = FlagsEnum(Byte, in_limited_service_mode=1, after_successful_plmn_selection=2) self._construct = FlagsEnum(
Byte, in_limited_service_mode=1, after_successful_plmn_selection=2)
# TS 51.011 Section 4.2.58 # TS 51.011 Section 4.2.58
class EF_PNN(LinFixedEF): class EF_PNN(LinFixedEF):
class FullNameForNetwork(BER_TLV_IE, tag=0x43): class FullNameForNetwork(BER_TLV_IE, tag=0x43):
# TS 24.008 10.5.3.5a # TS 24.008 10.5.3.5a
pass pass
class ShortNameForNetwork(BER_TLV_IE, tag=0x45): class ShortNameForNetwork(BER_TLV_IE, tag=0x45):
# TS 24.008 10.5.3.5a # TS 24.008 10.5.3.5a
pass pass
class NetworkNameCollection(TLV_IE_Collection, nested=[FullNameForNetwork, ShortNameForNetwork]): class NetworkNameCollection(TLV_IE_Collection, nested=[FullNameForNetwork, ShortNameForNetwork]):
pass pass
def __init__(self, fid='6fc5', sfid=None, name='EF.PNN', desc='PLMN Network Name'): def __init__(self, fid='6fc5', sfid=None, name='EF.PNN', desc='PLMN Network Name'):
super().__init__(fid, sfid=sfid, name=name, desc=desc) super().__init__(fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_PNN.NetworkNameCollection self._tlv = EF_PNN.NetworkNameCollection
# TS 51.011 Section 10.3.42 # TS 51.011 Section 10.3.42
class EF_OPL(LinFixedEF): class EF_OPL(LinFixedEF):
def __init__(self, fid='6fc6', sfid=None, name='EF.OPL', rec_len={8, 8}, desc='Operator PLMN List'): def __init__(self, fid='6fc6', sfid=None, name='EF.OPL', rec_len={8, 8}, desc='Operator PLMN List'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
self._construct = Struct('lai'/Bytes(5), 'pnn_record_id'/Int8ub) self._construct = Struct('lai'/Bytes(5), 'pnn_record_id'/Int8ub)
# TS 51.011 Section 10.3.44 + TS 31.102 4.2.62 # TS 51.011 Section 10.3.44 + TS 31.102 4.2.62
class EF_MBI(LinFixedEF): class EF_MBI(LinFixedEF):
def __init__(self, fid='6fc9', sfid=None, name='EF.MBI', rec_len={4, 5}, desc='Mailbox Identifier'): def __init__(self, fid='6fc9', sfid=None, name='EF.MBI', rec_len={4, 5}, desc='Mailbox Identifier'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
@@ -825,6 +938,8 @@ class EF_MBI(LinFixedEF):
'mbi_other'/Int8ub, 'mbi_videocall'/COptional(Int8ub)) 'mbi_other'/Int8ub, 'mbi_videocall'/COptional(Int8ub))
# TS 51.011 Section 10.3.45 + TS 31.102 4.2.63 # TS 51.011 Section 10.3.45 + TS 31.102 4.2.63
class EF_MWIS(LinFixedEF): class EF_MWIS(LinFixedEF):
def __init__(self, fid='6fca', sfid=None, name='EF.MWIS', rec_len={5, 6}, def __init__(self, fid='6fca', sfid=None, name='EF.MWIS', rec_len={5, 6},
desc='Message Waiting Indication Status'): desc='Message Waiting Indication Status'):
@@ -835,10 +950,13 @@ class EF_MWIS(LinFixedEF):
'num_waiting_other'/Int8ub, 'num_waiting_videomail'/COptional(Int8ub)) 'num_waiting_other'/Int8ub, 'num_waiting_videomail'/COptional(Int8ub))
# TS 51.011 Section 10.3.66 # TS 51.011 Section 10.3.66
class EF_SPDI(TransparentEF): class EF_SPDI(TransparentEF):
class ServiceProviderPLMN(BER_TLV_IE, tag=0x80): class ServiceProviderPLMN(BER_TLV_IE, tag=0x80):
# flexible numbers of 3-byte PLMN records # flexible numbers of 3-byte PLMN records
_construct = GreedyRange(BcdAdapter(Bytes(3))) _construct = GreedyRange(BcdAdapter(Bytes(3)))
class SPDI(BER_TLV_IE, tag=0xA3, nested=[ServiceProviderPLMN]): class SPDI(BER_TLV_IE, tag=0xA3, nested=[ServiceProviderPLMN]):
pass pass
def __init__(self, fid='6fcd', sfid=None, name='EF.SPDI', def __init__(self, fid='6fcd', sfid=None, name='EF.SPDI',
@@ -847,6 +965,8 @@ class EF_SPDI(TransparentEF):
self._tlv = EF_SPDI.SPDI self._tlv = EF_SPDI.SPDI
# TS 51.011 Section 10.3.51 # TS 51.011 Section 10.3.51
class EF_MMSN(LinFixedEF): class EF_MMSN(LinFixedEF):
def __init__(self, fid='6fce', sfid=None, name='EF.MMSN', rec_len={4, 20}, desc='MMS Notification'): def __init__(self, fid='6fce', sfid=None, name='EF.MMSN', rec_len={4, 20}, desc='MMS Notification'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len)
@@ -854,20 +974,27 @@ class EF_MMSN(LinFixedEF):
'mms_notification'/Bytes(this._.total_len-4), 'ext_record_nr'/Byte) 'mms_notification'/Bytes(this._.total_len-4), 'ext_record_nr'/Byte)
# TS 51.011 Annex K.1 # TS 51.011 Annex K.1
class MMS_Implementation(BER_TLV_IE, tag=0x80): class MMS_Implementation(BER_TLV_IE, tag=0x80):
_construct = FlagsEnum(Byte, WAP=1) _construct = FlagsEnum(Byte, WAP=1)
# TS 51.011 Section 10.3.53 # TS 51.011 Section 10.3.53
class EF_MMSICP(TransparentEF): class EF_MMSICP(TransparentEF):
class MMS_Relay_Server(BER_TLV_IE, tag=0x81): class MMS_Relay_Server(BER_TLV_IE, tag=0x81):
# 3GPP TS 23.140 # 3GPP TS 23.140
pass pass
class Interface_to_CN(BER_TLV_IE, tag=0x82): class Interface_to_CN(BER_TLV_IE, tag=0x82):
# 3GPP TS 23.140 # 3GPP TS 23.140
pass pass
class Gateway(BER_TLV_IE, tag=0x83): class Gateway(BER_TLV_IE, tag=0x83):
# Address, Type of address, Port, Service, AuthType, AuthId, AuthPass / 3GPP TS 23.140 # Address, Type of address, Port, Service, AuthType, AuthId, AuthPass / 3GPP TS 23.140
pass pass
class MMS_ConnectivityParamters(TLV_IE_Collection, class MMS_ConnectivityParamters(TLV_IE_Collection,
nested=[MMS_Implementation, MMS_Relay_Server, Interface_to_CN, Gateway]): nested=[MMS_Implementation, MMS_Relay_Server, Interface_to_CN, Gateway]):
pass pass
@@ -877,11 +1004,15 @@ class EF_MMSICP(TransparentEF):
self._tlv = EF_MMSICP.MMS_ConnectivityParamters self._tlv = EF_MMSICP.MMS_ConnectivityParamters
# TS 51.011 Section 10.3.54 # TS 51.011 Section 10.3.54
class EF_MMSUP(LinFixedEF): class EF_MMSUP(LinFixedEF):
class MMS_UserPref_ProfileName(BER_TLV_IE, tag=0x81): class MMS_UserPref_ProfileName(BER_TLV_IE, tag=0x81):
pass pass
class MMS_UserPref_Info(BER_TLV_IE, tag=0x82): class MMS_UserPref_Info(BER_TLV_IE, tag=0x82):
pass pass
class MMS_User_Preferences(TLV_IE_Collection, class MMS_User_Preferences(TLV_IE_Collection,
nested=[MMS_Implementation, MMS_UserPref_ProfileName, MMS_UserPref_Info]): nested=[MMS_Implementation, MMS_UserPref_ProfileName, MMS_UserPref_Info]):
pass pass
@@ -891,6 +1022,8 @@ class EF_MMSUP(LinFixedEF):
self._tlv = EF_MMSUP.MMS_User_Preferences self._tlv = EF_MMSUP.MMS_User_Preferences
# TS 51.011 Section 10.3.55 # TS 51.011 Section 10.3.55
class EF_MMSUCP(TransparentEF): class EF_MMSUCP(TransparentEF):
def __init__(self, fid='6fd2', sfid=None, name='EF.MMSUCP', size={1, None}, def __init__(self, fid='6fd2', sfid=None, name='EF.MMSUCP', size={1, None},
desc='MMS User Connectivity Parameters'): desc='MMS User Connectivity Parameters'):
@@ -905,25 +1038,33 @@ class DF_GSM(CardDF):
EF_IMSI(), EF_IMSI(),
EF_Kc(), EF_Kc(),
EF_PLMNsel(), EF_PLMNsel(),
TransparentEF('6f31', None, 'EF.HPPLMN', 'Higher Priority PLMN search period'), TransparentEF('6f31', None, 'EF.HPPLMN',
'Higher Priority PLMN search period'),
EF_ACMmax(), EF_ACMmax(),
EF_ServiceTable('6f38', None, 'EF.SST', 'SIM service table', table=EF_SST_map, size={2,16}), EF_ServiceTable('6f38', None, 'EF.SST',
CyclicEF('6f39', None, 'EF.ACM', 'Accumulated call meter', rec_len={3,3}), 'SIM service table', table=EF_SST_map, size={2, 16}),
CyclicEF('6f39', None, 'EF.ACM',
'Accumulated call meter', rec_len={3, 3}),
TransparentEF('6f3e', None, 'EF.GID1', 'Group Identifier Level 1'), TransparentEF('6f3e', None, 'EF.GID1', 'Group Identifier Level 1'),
TransparentEF('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'), TransparentEF('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'),
EF_SPN(), EF_SPN(),
TransparentEF('6f41', None, 'EF.PUCT', 'Price per unit and currency table', size={5,5}), TransparentEF('6f41', None, 'EF.PUCT',
'Price per unit and currency table', size={5, 5}),
EF_CBMI(), EF_CBMI(),
TransparentEF('6f7f', None, 'EF.BCCH', 'Broadcast control channels', size={16,16}), TransparentEF('6f7f', None, 'EF.BCCH',
'Broadcast control channels', size={16, 16}),
EF_ACC(), EF_ACC(),
EF_PLMNsel('6f7b', None, 'EF.FPLMN', 'Forbidden PLMNs', size={12,12}), EF_PLMNsel('6f7b', None, 'EF.FPLMN',
'Forbidden PLMNs', size={12, 12}),
EF_LOCI(), EF_LOCI(),
EF_AD(), EF_AD(),
TransparentEF('6fa3', None, 'EF.Phase', 'Phase identification', size={1,1}), TransparentEF('6fa3', None, 'EF.Phase',
'Phase identification', size={1, 1}),
EF_VGCS(), EF_VGCS(),
EF_VGCSS(), EF_VGCSS(),
EF_VGCS('6fb3', None, 'EF.VBS', 'Voice Broadcast Service'), EF_VGCS('6fb3', None, 'EF.VBS', 'Voice Broadcast Service'),
EF_VGCSS('6fb4', None, 'EF.VBSS', 'Voice Broadcast Service Status'), EF_VGCSS('6fb4', None, 'EF.VBSS',
'Voice Broadcast Service Status'),
EF_eMLPP(), EF_eMLPP(),
EF_AAeM(), EF_AAeM(),
EF_CBMID(), EF_CBMID(),
@@ -939,7 +1080,8 @@ class DF_GSM(CardDF):
'User controlled PLMN Selector with Access Technology'), 'User controlled PLMN Selector with Access Technology'),
EF_xPLMNwAcT('6f61', None, 'EF.OPLMNwAcT', EF_xPLMNwAcT('6f61', None, 'EF.OPLMNwAcT',
'Operator controlled PLMN Selector with Access Technology'), 'Operator controlled PLMN Selector with Access Technology'),
EF_xPLMNwAcT('6f62', None, 'EF.HPLMNwAcT', 'HPLMN Selector with Access Technology'), EF_xPLMNwAcT('6f62', None, 'EF.HPLMNwAcT',
'HPLMN Selector with Access Technology'),
EF_CPBCCH(), EF_CPBCCH(),
EF_InvScan(), EF_InvScan(),
EF_PNN(), EF_PNN(),
@@ -947,7 +1089,8 @@ class DF_GSM(CardDF):
EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'), EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'),
EF_MBI(), EF_MBI(),
EF_MWIS(), EF_MWIS(),
EF_ADN('6fcb', None, 'EF.CFIS', 'Call Forwarding Indication Status'), EF_ADN('6fcb', None, 'EF.CFIS',
'Call Forwarding Indication Status'),
EF_EXT('6fc8', None, 'EF.EXT6', 'Externsion6 (MBDN)'), EF_EXT('6fc8', None, 'EF.EXT6', 'Externsion6 (MBDN)'),
EF_EXT('6fcc', None, 'EF.EXT7', 'Externsion7 (CFIS)'), EF_EXT('6fcc', None, 'EF.EXT7', 'Externsion7 (CFIS)'),
EF_SPDI(), EF_SPDI(),
@@ -959,6 +1102,7 @@ class DF_GSM(CardDF):
] ]
self.add_files(files) self.add_files(files)
class CardProfileSIM(CardProfile): class CardProfileSIM(CardProfile):
ORDER = 2 ORDER = 2
@@ -1001,7 +1145,8 @@ class CardProfileSIM(CardProfile):
}, },
} }
super().__init__('SIM', desc='GSM SIM Card', cla="a0", sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM()], sw=sw) super().__init__('SIM', desc='GSM SIM Card', cla="a0",
sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM()], sw=sw)
@staticmethod @staticmethod
def decode_select_response(resp_hex: str) -> object: def decode_select_response(resp_hex: str) -> object:
@@ -1021,8 +1166,10 @@ class CardProfileSIM(CardProfile):
'proprietary_info': {}, 'proprietary_info': {},
} }
ret['file_id'] = b2h(resp_bin[4:6]) ret['file_id'] = b2h(resp_bin[4:6])
ret['proprietary_info']['available_memory'] = int.from_bytes(resp_bin[2:4], 'big') ret['proprietary_info']['available_memory'] = int.from_bytes(
file_type = type_of_file_map[resp_bin[6]] if resp_bin[6] in type_of_file_map else resp_bin[6] resp_bin[2:4], 'big')
file_type = type_of_file_map[resp_bin[6]
] if resp_bin[6] in type_of_file_map else resp_bin[6]
ret['file_descriptor']['file_type'] = file_type ret['file_descriptor']['file_type'] = file_type
if file_type in ['mf', 'df']: if file_type in ['mf', 'df']:
ret['file_characteristics'] = b2h(resp_bin[13:14]) ret['file_characteristics'] = b2h(resp_bin[13:14])
@@ -1031,7 +1178,8 @@ class CardProfileSIM(CardProfile):
ret['num_chv_unblock_adm_codes'] = int(resp_bin[16]) ret['num_chv_unblock_adm_codes'] = int(resp_bin[16])
# CHV / UNBLOCK CHV stats # CHV / UNBLOCK CHV stats
elif file_type in ['working_ef']: elif file_type in ['working_ef']:
file_struct = struct_of_file_map[resp_bin[13]] if resp_bin[13] in struct_of_file_map else resp_bin[13] file_struct = struct_of_file_map[resp_bin[13]
] if resp_bin[13] in struct_of_file_map else resp_bin[13]
ret['file_descriptor']['structure'] = file_struct ret['file_descriptor']['structure'] = file_struct
ret['access_conditions'] = b2h(resp_bin[8:10]) ret['access_conditions'] = b2h(resp_bin[8:10])
if resp_bin[11] & 0x01 == 0: if resp_bin[11] & 0x01 == 0:

View File

@@ -29,41 +29,50 @@ from typing import Optional, List, Dict, Any, Tuple
# just to differentiate strings of hex nibbles from everything else # just to differentiate strings of hex nibbles from everything else
Hexstr = str Hexstr = str
def h2b(s: Hexstr) -> bytearray: def h2b(s: Hexstr) -> bytearray:
"""convert from a string of hex nibbles to a sequence of bytes""" """convert from a string of hex nibbles to a sequence of bytes"""
return bytearray.fromhex(s) return bytearray.fromhex(s)
def b2h(b: bytearray) -> Hexstr: def b2h(b: bytearray) -> Hexstr:
"""convert from a sequence of bytes to a string of hex nibbles""" """convert from a sequence of bytes to a string of hex nibbles"""
return ''.join(['%02x' % (x) for x in b]) return ''.join(['%02x' % (x) for x in b])
def h2i(s: Hexstr) -> List[int]: def h2i(s: Hexstr) -> List[int]:
"""convert from a string of hex nibbles to a list of integers""" """convert from a string of hex nibbles to a list of integers"""
return [(int(x, 16) << 4)+int(y, 16) for x, y in zip(s[0::2], s[1::2])] return [(int(x, 16) << 4)+int(y, 16) for x, y in zip(s[0::2], s[1::2])]
def i2h(s: List[int]) -> Hexstr: def i2h(s: List[int]) -> Hexstr:
"""convert from a list of integers to a string of hex nibbles""" """convert from a list of integers to a string of hex nibbles"""
return ''.join(['%02x' % (x) for x in s]) return ''.join(['%02x' % (x) for x in s])
def h2s(s: Hexstr) -> str: def h2s(s: Hexstr) -> str:
"""convert from a string of hex nibbles to an ASCII string""" """convert from a string of hex nibbles to an ASCII string"""
return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2]) return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2])
if int(x + y, 16) != 0xff]) if int(x + y, 16) != 0xff])
def s2h(s: str) -> Hexstr: def s2h(s: str) -> Hexstr:
"""convert from an ASCII string to a string of hex nibbles""" """convert from an ASCII string to a string of hex nibbles"""
b = bytearray() b = bytearray()
b.extend(map(ord, s)) b.extend(map(ord, s))
return b2h(b) return b2h(b)
def i2s(s: List[int]) -> str: def i2s(s: List[int]) -> str:
"""convert from a list of integers to an ASCII string""" """convert from a list of integers to an ASCII string"""
return ''.join([chr(x) for x in s]) return ''.join([chr(x) for x in s])
def swap_nibbles(s: Hexstr) -> Hexstr: def swap_nibbles(s: Hexstr) -> Hexstr:
"""swap the nibbles in a hex string""" """swap the nibbles in a hex string"""
return ''.join([x+y for x, y in zip(s[1::2], s[0::2])]) return ''.join([x+y for x, y in zip(s[1::2], s[0::2])])
def rpad(s: str, l: int, c='f') -> str: def rpad(s: str, l: int, c='f') -> str:
"""pad string on the right side. """pad string on the right side.
Args: Args:
@@ -75,6 +84,7 @@ def rpad(s:str, l:int, c='f') -> str:
""" """
return s + c * (l - len(s)) return s + c * (l - len(s))
def lpad(s: str, l: int, c='f') -> str: def lpad(s: str, l: int, c='f') -> str:
"""pad string on the left side. """pad string on the left side.
Args: Args:
@@ -86,9 +96,11 @@ def lpad(s:str, l:int, c='f') -> str:
""" """
return c * (l - len(s)) + s return c * (l - len(s)) + s
def half_round_up(n: int) -> int: def half_round_up(n: int) -> int:
return (n + 1)//2 return (n + 1)//2
def str_sanitize(s: str) -> str: def str_sanitize(s: str) -> str:
"""replace all non printable chars, line breaks and whitespaces, with ' ', make sure that """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that
there are no whitespaces at the end and at the beginning of the string. there are no whitespaces at the end and at the beginning of the string.
@@ -107,10 +119,12 @@ def str_sanitize(s:str) -> str:
# poor man's COMPREHENSION-TLV decoder. # poor man's COMPREHENSION-TLV decoder.
######################################################################### #########################################################################
def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]: def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
"""Parse a single Tag according to ETSI TS 101 220 Section 7.1.1""" """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
if binary[0] in [0x00, 0x80, 0xff]: if binary[0] in [0x00, 0x80, 0xff]:
raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary)) raise ValueError("Found illegal value 0x%02x in %s" %
(binary[0], binary))
if binary[0] == 0x7f: if binary[0] == 0x7f:
# three-byte tag # three-byte tag
tag = binary[0] << 16 | binary[1] << 8 | binary[2] tag = binary[0] << 16 | binary[1] << 8 | binary[2]
@@ -122,10 +136,12 @@ def comprehensiontlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
tag = binary[0] tag = binary[0]
return (tag, binary[1:]) return (tag, binary[1:])
def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]: def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
"""Parse a single Tag according to ETSI TS 101 220 Section 7.1.1""" """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
if binary[0] in [0x00, 0x80, 0xff]: if binary[0] in [0x00, 0x80, 0xff]:
raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary)) raise ValueError("Found illegal value 0x%02x in %s" %
(binary[0], binary))
if binary[0] == 0x7f: if binary[0] == 0x7f:
# three-byte tag # three-byte tag
tag = (binary[1] & 0x7f) << 8 tag = (binary[1] & 0x7f) << 8
@@ -138,6 +154,7 @@ def comprehensiontlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
compr = True if binary[0] & 0x80 else False compr = True if binary[0] & 0x80 else False
return ({'comprehension': compr, 'tag': tag}, binary[1:]) return ({'comprehension': compr, 'tag': tag}, binary[1:])
def comprehensiontlv_encode_tag(tag) -> bytes: def comprehensiontlv_encode_tag(tag) -> bytes:
"""Encode a single Tag according to ETSI TS 101 220 Section 7.1.1""" """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
# permit caller to specify tag also as integer value # permit caller to specify tag also as integer value
@@ -161,6 +178,7 @@ def comprehensiontlv_encode_tag(tag) -> bytes:
# length value coding is equal to BER-TLV # length value coding is equal to BER-TLV
def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]: def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
"""Parse a single TLV IE at the start of the given binary data. """Parse a single TLV IE at the start of the given binary data.
Args: Args:
@@ -175,7 +193,6 @@ def comprehensiontlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
return (tagdict, length, value, remainder) return (tagdict, length, value, remainder)
######################################################################### #########################################################################
# poor man's BER-TLV decoder. To be a more sophisticated OO library later # poor man's BER-TLV decoder. To be a more sophisticated OO library later
######################################################################### #########################################################################
@@ -204,6 +221,7 @@ def bertlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]:
i += 1 i += 1
return tag, binary[i:] return tag, binary[i:]
def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]: def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
"""Parse a single Tag value according to ITU-T X.690 8.1.2 """Parse a single Tag value according to ITU-T X.690 8.1.2
Args: Args:
@@ -227,6 +245,7 @@ def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
i += 1 i += 1
return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:]) return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:])
def bertlv_encode_tag(t) -> bytes: def bertlv_encode_tag(t) -> bytes:
"""Encode a single Tag value according to ITU-T X.690 8.1.2 """Encode a single Tag value according to ITU-T X.690 8.1.2
""" """
@@ -272,6 +291,7 @@ def bertlv_encode_tag(t) -> bytes:
break break
return tag_bytes return tag_bytes
def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]: def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
"""Parse a single Length value according to ITU-T X.690 8.1.3; """Parse a single Length value according to ITU-T X.690 8.1.3;
only the definite form is supported here. only the definite form is supported here.
@@ -290,6 +310,7 @@ def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:
length |= binary[i] length |= binary[i]
return (length, binary[1+num_len_oct:]) return (length, binary[1+num_len_oct:])
def bertlv_encode_len(length: int) -> bytes: def bertlv_encode_len(length: int) -> bytes:
"""Encode a single Length value according to ITU-T X.690 8.1.3; """Encode a single Length value according to ITU-T X.690 8.1.3;
only the definite form is supported here. only the definite form is supported here.
@@ -311,6 +332,7 @@ def bertlv_encode_len(length:int) -> bytes:
else: else:
raise ValueError("Length > 32bits not supported") raise ValueError("Length > 32bits not supported")
def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]: def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
"""Parse a single TLV IE at the start of the given binary data. """Parse a single TLV IE at the start of the given binary data.
Args: Args:
@@ -325,7 +347,6 @@ def bertlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
return (tagdict, length, value, remainder) return (tagdict, length, value, remainder)
# IMSI encoded format: # IMSI encoded format:
# For IMSI 0123456789ABCDE: # For IMSI 0123456789ABCDE:
# #
@@ -343,11 +364,13 @@ def bertlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
def enc_imsi(imsi: str): def enc_imsi(imsi: str):
"""Converts a string IMSI into the encoded value of the EF""" """Converts a string IMSI into the encoded value of the EF"""
l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator l = half_round_up(
len(imsi) + 1) # Required bytes - include space for odd/even indicator
oe = len(imsi) & 1 # Odd (1) / Even (0) oe = len(imsi) & 1 # Odd (1) / Even (0)
ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15))) ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
return ei return ei
def dec_imsi(ef: Hexstr) -> Optional[str]: def dec_imsi(ef: Hexstr) -> Optional[str]:
"""Converts an EF value to the IMSI string representation""" """Converts an EF value to the IMSI string representation"""
if len(ef) < 4: if len(ef) < 4:
@@ -366,12 +389,15 @@ def dec_imsi(ef:Hexstr) -> Optional[str]:
imsi = swapped[1:] imsi = swapped[1:]
return imsi return imsi
def dec_iccid(ef: Hexstr) -> str: def dec_iccid(ef: Hexstr) -> str:
return swap_nibbles(ef).strip('f') return swap_nibbles(ef).strip('f')
def enc_iccid(iccid: str) -> Hexstr: def enc_iccid(iccid: str) -> Hexstr:
return swap_nibbles(rpad(iccid, 20)) return swap_nibbles(rpad(iccid, 20))
def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr: def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
"""Converts integer MCC/MNC into 3 bytes for EF""" """Converts integer MCC/MNC into 3 bytes for EF"""
@@ -398,6 +424,7 @@ def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr:
return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0]) return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0])
def dec_plmn(threehexbytes: Hexstr) -> dict: def dec_plmn(threehexbytes: Hexstr) -> dict:
res = {'mcc': "0", 'mnc': "0"} res = {'mcc': "0", 'mnc': "0"}
dec_mcc_from_plmn_str(threehexbytes) dec_mcc_from_plmn_str(threehexbytes)
@@ -405,6 +432,7 @@ def dec_plmn(threehexbytes:Hexstr) -> dict:
res['mnc'] = dec_mnc_from_plmn_str(threehexbytes) res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
return res return res
def dec_spn(ef): def dec_spn(ef):
"""Obsolete, kept for API compatibility""" """Obsolete, kept for API compatibility"""
from ts_51_011 import EF_SPN from ts_51_011 import EF_SPN
@@ -414,6 +442,7 @@ def dec_spn(ef):
name = abstract_data['spn'] name = abstract_data['spn']
return (name, show_in_hplmn, hide_in_oplmn) return (name, show_in_hplmn, hide_in_oplmn)
def enc_spn(name: str, show_in_hplmn=False, hide_in_oplmn=False): def enc_spn(name: str, show_in_hplmn=False, hide_in_oplmn=False):
"""Obsolete, kept for API compatibility""" """Obsolete, kept for API compatibility"""
from ts_51_011 import EF_SPN from ts_51_011 import EF_SPN
@@ -424,10 +453,13 @@ def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False):
} }
return EF_SPN().encode_hex(abstract_data) return EF_SPN().encode_hex(abstract_data)
def hexstr_to_Nbytearr(s, nbytes): def hexstr_to_Nbytearr(s, nbytes):
return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))] return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))]
# Accepts hex string representing three bytes # Accepts hex string representing three bytes
def dec_mcc_from_plmn(plmn: Hexstr) -> int: def dec_mcc_from_plmn(plmn: Hexstr) -> int:
ia = h2i(plmn) ia = h2i(plmn)
digit1 = ia[0] & 0x0F # 1st byte, LSB digit1 = ia[0] & 0x0F # 1st byte, LSB
@@ -437,6 +469,7 @@ def dec_mcc_from_plmn(plmn:Hexstr) -> int:
return 0xFFF # 4095 return 0xFFF # 4095
return derive_mcc(digit1, digit2, digit3) return derive_mcc(digit1, digit2, digit3)
def dec_mcc_from_plmn_str(plmn: Hexstr) -> str: def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
digit1 = plmn[1] # 1st byte, LSB digit1 = plmn[1] # 1st byte, LSB
digit2 = plmn[0] # 1st byte, MSB digit2 = plmn[0] # 1st byte, MSB
@@ -444,6 +477,7 @@ def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
res = digit1 + digit2 + digit3 res = digit1 + digit2 + digit3
return res.upper().strip("F") return res.upper().strip("F")
def dec_mnc_from_plmn(plmn: Hexstr) -> int: def dec_mnc_from_plmn(plmn: Hexstr) -> int:
ia = h2i(plmn) ia = h2i(plmn)
digit1 = ia[2] & 0x0F # 3rd byte, LSB digit1 = ia[2] & 0x0F # 3rd byte, LSB
@@ -453,6 +487,7 @@ def dec_mnc_from_plmn(plmn:Hexstr) -> int:
return 0xFFF # 4095 return 0xFFF # 4095
return derive_mnc(digit1, digit2, digit3) return derive_mnc(digit1, digit2, digit3)
def dec_mnc_from_plmn_str(plmn: Hexstr) -> str: def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
digit1 = plmn[5] # 3rd byte, LSB digit1 = plmn[5] # 3rd byte, LSB
digit2 = plmn[4] # 3rd byte, MSB digit2 = plmn[4] # 3rd byte, MSB
@@ -460,6 +495,7 @@ def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
res = digit1 + digit2 + digit3 res = digit1 + digit2 + digit3
return res.upper().strip("F") return res.upper().strip("F")
def dec_act(twohexbytes: Hexstr) -> List[str]: def dec_act(twohexbytes: Hexstr) -> List[str]:
act_list = [ act_list = [
{'bit': 15, 'name': "UTRAN"}, {'bit': 15, 'name': "UTRAN"},
@@ -491,17 +527,21 @@ def dec_act(twohexbytes:Hexstr) -> List[str]:
sel.append(a['name']) sel.append(a['name'])
return sel return sel
def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]: def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
res = {'mcc': "0", 'mnc': "0", 'act': []} res = {'mcc': "0", 'mnc': "0", 'act': []}
plmn_chars = 6 plmn_chars = 6
act_chars = 4 act_chars = 4
plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars) # first three bytes (six ascii hex chars)
act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes plmn_str = fivehexbytes[:plmn_chars]
# two bytes after first three bytes
act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars]
res['mcc'] = dec_mcc_from_plmn_str(plmn_str) res['mcc'] = dec_mcc_from_plmn_str(plmn_str)
res['mnc'] = dec_mnc_from_plmn_str(plmn_str) res['mnc'] = dec_mnc_from_plmn_str(plmn_str)
res['act'] = dec_act(act_str) res['act'] = dec_act(act_str)
return res return res
def format_xplmn_w_act(hexstr): def format_xplmn_w_act(hexstr):
s = "" s = ""
for rec_data in hexstr_to_Nbytearr(hexstr, 5): for rec_data in hexstr_to_Nbytearr(hexstr, 5):
@@ -509,10 +549,12 @@ def format_xplmn_w_act(hexstr):
if rec_info['mcc'] == "" and rec_info['mnc'] == "": if rec_info['mcc'] == "" and rec_info['mnc'] == "":
rec_str = "unused" rec_str = "unused"
else: else:
rec_str = "MCC: %s MNC: %s AcT: %s" % (rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act'])) rec_str = "MCC: %s MNC: %s AcT: %s" % (
rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
s += "\t%s # %s\n" % (rec_data, rec_str) s += "\t%s # %s\n" % (rec_data, rec_str)
return s return s
def dec_loci(hexstr): def dec_loci(hexstr):
res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0} res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
res['tmsi'] = hexstr[:8] res['tmsi'] = hexstr[:8]
@@ -522,8 +564,10 @@ def dec_loci(hexstr):
res['status'] = h2i(hexstr[20:22]) res['status'] = h2i(hexstr[20:22])
return res return res
def dec_psloci(hexstr): def dec_psloci(hexstr):
res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0} res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0,
'mnc': 0, 'lac': '', 'rac': '', 'status': 0}
res['p-tmsi'] = hexstr[:8] res['p-tmsi'] = hexstr[:8]
res['p-tmsi-sig'] = hexstr[8:14] res['p-tmsi-sig'] = hexstr[8:14]
res['mcc'] = dec_mcc_from_plmn(hexstr[14:20]) res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
@@ -533,6 +577,7 @@ def dec_psloci(hexstr):
res['status'] = h2i(hexstr[26:28]) res['status'] = h2i(hexstr[26:28])
return res return res
def dec_epsloci(hexstr): def dec_epsloci(hexstr):
res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0} res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
res['guti'] = hexstr[:24] res['guti'] = hexstr[:24]
@@ -543,14 +588,17 @@ def dec_epsloci(hexstr):
res['status'] = h2i(hexstr[34:36]) res['status'] = h2i(hexstr[34:36])
return res return res
def dec_xplmn(threehexbytes: Hexstr) -> dict: def dec_xplmn(threehexbytes: Hexstr) -> dict:
res = {'mcc': 0, 'mnc': 0, 'act': []} res = {'mcc': 0, 'mnc': 0, 'act': []}
plmn_chars = 6 plmn_chars = 6
plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars) # first three bytes (six ascii hex chars)
plmn_str = threehexbytes[:plmn_chars]
res['mcc'] = dec_mcc_from_plmn(plmn_str) res['mcc'] = dec_mcc_from_plmn(plmn_str)
res['mnc'] = dec_mnc_from_plmn(plmn_str) res['mnc'] = dec_mnc_from_plmn(plmn_str)
return res return res
def format_xplmn(hexstr: Hexstr) -> str: def format_xplmn(hexstr: Hexstr) -> str:
s = "" s = ""
for rec_data in hexstr_to_Nbytearr(hexstr, 3): for rec_data in hexstr_to_Nbytearr(hexstr, 3):
@@ -558,10 +606,12 @@ def format_xplmn(hexstr:Hexstr) -> str:
if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF: if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
rec_str = "unused" rec_str = "unused"
else: else:
rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc']) rec_str = "MCC: %03d MNC: %03d" % (
rec_info['mcc'], rec_info['mnc'])
s += "\t%s # %s\n" % (rec_data, rec_str) s += "\t%s # %s\n" % (rec_data, rec_str)
return s return s
def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr: def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
""" """
Run the milenage algorithm to calculate OPC from Ki and OP Run the milenage algorithm to calculate OPC from Ki and OP
@@ -578,14 +628,17 @@ def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr:
opc_bytes = aes.encrypt(op_bytes) opc_bytes = aes.encrypt(op_bytes)
return b2h(strxor(opc_bytes, op_bytes)) return b2h(strxor(opc_bytes, op_bytes))
def calculate_luhn(cc) -> int: def calculate_luhn(cc) -> int:
""" """
Calculate Luhn checksum used in e.g. ICCID and IMEI Calculate Luhn checksum used in e.g. ICCID and IMEI
""" """
num = list(map(int, str(cc))) num = list(map(int, str(cc)))
check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10 check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10))
for d in num[::-2]]) % 10
return 0 if check_digit == 10 else check_digit return 0 if check_digit == 10 else check_digit
def mcc_from_imsi(imsi: str) -> Optional[str]: def mcc_from_imsi(imsi: str) -> Optional[str]:
""" """
Derive the MCC (Mobile Country Code) from the first three digits of an IMSI Derive the MCC (Mobile Country Code) from the first three digits of an IMSI
@@ -598,6 +651,7 @@ def mcc_from_imsi(imsi:str) -> Optional[str]:
else: else:
return None return None
def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]: def mnc_from_imsi(imsi: str, long: bool = False) -> Optional[str]:
""" """
Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI
@@ -613,6 +667,7 @@ def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
else: else:
return None return None
def derive_mcc(digit1: int, digit2: int, digit3: int) -> int: def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
""" """
Derive decimal representation of the MCC (Mobile Country Code) Derive decimal representation of the MCC (Mobile Country Code)
@@ -630,6 +685,7 @@ def derive_mcc(digit1:int, digit2:int, digit3:int) -> int:
return mcc return mcc
def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int: def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
""" """
Derive decimal representation of the MNC (Mobile Network Code) Derive decimal representation of the MNC (Mobile Network Code)
@@ -650,6 +706,7 @@ def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int:
return mnc return mnc
def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]: def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
""" """
Decode MSISDN from EF.MSISDN or EF.ADN (same structure). Decode MSISDN from EF.MSISDN or EF.ADN (same structure).
@@ -673,7 +730,8 @@ def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
if bcd_len == 0xff: if bcd_len == 0xff:
return None return None
elif bcd_len > 11 or bcd_len < 1: elif bcd_len > 11 or bcd_len < 1:
raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len) raise ValueError(
"Length of MSISDN (%d bytes) is out of range" % bcd_len)
# Parse ToN / NPI # Parse ToN / NPI
ton = (msisdn_lhv[1] >> 4) & 0x07 ton = (msisdn_lhv[1] >> 4) & 0x07
@@ -691,6 +749,7 @@ def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]:
return (npi, ton, msisdn) return (npi, ton, msisdn)
def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr: def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
""" """
Encode MSISDN as LHV so it can be stored to EF.MSISDN. Encode MSISDN as LHV so it can be stored to EF.MSISDN.
@@ -757,10 +816,12 @@ def dec_st(st, table="sim") -> str:
# Byte X contains info about Services num (8X-7) to num (8X) # Byte X contains info about Services num (8X-7) to num (8X)
# bit = 1: service available # bit = 1: service available
# bit = 0: service not available # bit = 0: service not available
avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j]) avail_st += '\tService %d - %s\n' % (
(8*i) + j, lookup_map[(8*i) + j])
byte = byte >> 1 byte = byte >> 1
return avail_st return avail_st
def first_TLV_parser(bytelist): def first_TLV_parser(bytelist):
''' '''
first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205]) first_TLV_parser([0xAA, 0x02, 0xAB, 0xCD, 0xFF, 0x00]) -> (170, 2, [171, 205])
@@ -779,6 +840,7 @@ def first_TLV_parser(bytelist):
Val = bytelist[2:2+Len] Val = bytelist[2:2+Len]
return (Tag, Len, Val) return (Tag, Len, Val)
def TLV_parser(bytelist): def TLV_parser(bytelist):
''' '''
TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...] TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
@@ -800,6 +862,7 @@ def TLV_parser(bytelist):
bytelist = bytelist[L+2:] bytelist = bytelist[L+2:]
return ret return ret
def enc_st(st, service, state=1): def enc_st(st, service, state=1):
""" """
Encodes the EF S/U/IST/EST and returns the updated Service Table Encodes the EF S/U/IST/EST and returns the updated Service Table
@@ -843,6 +906,7 @@ def enc_st(st, service, state=1):
return s return s
def dec_addr_tlv(hexstr): def dec_addr_tlv(hexstr):
""" """
Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm. Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm.
@@ -889,6 +953,7 @@ def dec_addr_tlv(hexstr):
return (None, None) return (None, None)
def enc_addr_tlv(addr, addr_type='00'): def enc_addr_tlv(addr, addr_type='00'):
""" """
Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm. Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm.
@@ -916,6 +981,7 @@ def enc_addr_tlv(addr, addr_type='00'):
return s return s
def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool: def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
""" """
Check if a string is a valid hexstring Check if a string is a valid hexstring
@@ -938,6 +1004,7 @@ def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool:
except: except:
return False return False
def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr: def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
""" """
The ADM pin can be supplied either in its hexadecimal form or as The ADM pin can be supplied either in its hexadecimal form or as
@@ -961,12 +1028,15 @@ def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr:
try: try:
try_encode = h2b(pin_adm) try_encode = h2b(pin_adm)
except ValueError: except ValueError:
raise ValueError("PIN-ADM needs to be hex encoded using this option") raise ValueError(
"PIN-ADM needs to be hex encoded using this option")
else: else:
raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)") raise ValueError(
"PIN-ADM needs to be exactly 16 digits (hex encoded)")
return pin_adm return pin_adm
def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'): def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'):
""" """
Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm. Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm.
@@ -983,6 +1053,7 @@ def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='
content = rpad(content, len(hexstr)) content = rpad(content, len(hexstr))
return content return content
def dec_ePDGSelection(sixhexbytes): def dec_ePDGSelection(sixhexbytes):
""" """
Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm. Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
@@ -996,15 +1067,18 @@ def dec_ePDGSelection(sixhexbytes):
# first three bytes (six ascii hex chars) # first three bytes (six ascii hex chars)
plmn_str = sixhexbytes[:plmn_chars] plmn_str = sixhexbytes[:plmn_chars]
# two bytes after first three bytes # two bytes after first three bytes
epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars] epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars +
epdg_priority_chars]
# one byte after first five bytes # one byte after first five bytes
epdg_fqdn_format_str = sixhexbytes[plmn_chars + epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars] epdg_fqdn_format_str = sixhexbytes[plmn_chars +
epdg_priority_chars:plmn_chars + epdg_priority_chars + epdg_fqdn_format_chars]
res['mcc'] = dec_mcc_from_plmn(plmn_str) res['mcc'] = dec_mcc_from_plmn(plmn_str)
res['mnc'] = dec_mnc_from_plmn(plmn_str) res['mnc'] = dec_mnc_from_plmn(plmn_str)
res['epdg_priority'] = epdg_priority_str res['epdg_priority'] = epdg_priority_str
res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN' res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
return res return res
def format_ePDGSelection(hexstr): def format_ePDGSelection(hexstr):
ePDGSelection_info_tag_chars = 2 ePDGSelection_info_tag_chars = 2
ePDGSelection_info_tag_str = hexstr[:2] ePDGSelection_info_tag_str = hexstr[:2]
@@ -1031,17 +1105,20 @@ def format_ePDGSelection(hexstr):
content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:] content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
# Right pad to prevent index out of range - multiple of 6 bytes # Right pad to prevent index out of range - multiple of 6 bytes
content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12))) content_str = rpad(content_str, len(content_str) +
(12 - (len(content_str) % 12)))
for rec_data in hexstr_to_Nbytearr(content_str, 6): for rec_data in hexstr_to_Nbytearr(content_str, 6):
rec_info = dec_ePDGSelection(rec_data) rec_info = dec_ePDGSelection(rec_data)
if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF: if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
rec_str = "unused" rec_str = "unused"
else: else:
rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \ rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \
(rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format']) (rec_info['mcc'], rec_info['mnc'],
rec_info['epdg_priority'], rec_info['epdg_fqdn_format'])
s += "\t%s # %s\n" % (rec_data, rec_str) s += "\t%s # %s\n" % (rec_data, rec_str)
return s return s
def get_addr_type(addr): def get_addr_type(addr):
""" """
Validates the given address and returns it's type (FQDN or IPv4 or IPv6) Validates the given address and returns it's type (FQDN or IPv4 or IPv6)
@@ -1093,6 +1170,7 @@ def get_addr_type(addr):
return None return None
def sw_match(sw: str, pattern: str) -> bool: def sw_match(sw: str, pattern: str) -> bool:
"""Match given SW against given pattern.""" """Match given SW against given pattern."""
# Create a masked version of the returned status word # Create a masked version of the returned status word
@@ -1108,6 +1186,7 @@ def sw_match(sw:str, pattern:str) -> bool:
# Compare the masked version against the pattern # Compare the masked version against the pattern
return sw_masked == pattern return sw_masked == pattern
def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1, def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
align_left: bool = True) -> str: align_left: bool = True) -> str:
"""Pretty print a list of strings into a tabulated form. """Pretty print a list of strings into a tabulated form.
@@ -1140,17 +1219,21 @@ def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1,
table.append(format_str_row % tuple(str_list_row)) table.append(format_str_row % tuple(str_list_row))
return '\n'.join(table) return '\n'.join(table)
def auto_int(x): def auto_int(x):
"""Helper function for argparse to accept hexadecimal integers.""" """Helper function for argparse to accept hexadecimal integers."""
return int(x, 0) return int(x, 0)
class JsonEncoder(json.JSONEncoder): class JsonEncoder(json.JSONEncoder):
"""Extend the standard library JSONEncoder with support for more types.""" """Extend the standard library JSONEncoder with support for more types."""
def default(self, o): def default(self, o):
if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray): if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
return b2h(o) return b2h(o)
return json.JSONEncoder.default(self, o) return json.JSONEncoder.default(self, o)
def boxed_heading_str(heading, width=80): def boxed_heading_str(heading, width=80):
"""Generate a string that contains a boxed heading.""" """Generate a string that contains a boxed heading."""
# Auto-enlarge box if heading exceeds length # Auto-enlarge box if heading exceeds length
@@ -1164,12 +1247,12 @@ def boxed_heading_str(heading, width=80):
return res return res
class DataObject(abc.ABC): class DataObject(abc.ABC):
"""A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one """A DataObject (DO) in the sense of ISO 7816-4. Contrary to 'normal' TLVs where one
simply has any number of different TLVs that may occur in any order at any point, ISO 7816 simply has any number of different TLVs that may occur in any order at any point, ISO 7816
has the habit of specifying TLV data but with very spcific ordering, or specific choices of has the habit of specifying TLV data but with very spcific ordering, or specific choices of
tags at specific points in a stream. This class tries to represent this.""" tags at specific points in a stream. This class tries to represent this."""
def __init__(self, name: str, desc: str = None, tag: int = None): def __init__(self, name: str, desc: str = None, tag: int = None):
""" """
Args: Args:
@@ -1236,7 +1319,8 @@ class DataObject(abc.ABC):
bytes remaining at end of 'do' after parsing one TLV/DO. bytes remaining at end of 'do' after parsing one TLV/DO.
""" """
if do[0] != self.tag: if do[0] != self.tag:
raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag)) raise ValueError('%s: Can only decode tag 0x%02x' %
(self, self.tag))
length = do[1] length = do[1]
val = do[2:2+length] val = do[2:2+length]
self.from_bytes(val) self.from_bytes(val)
@@ -1270,8 +1354,10 @@ class DataObject(abc.ABC):
def encode(self) -> bytes: def encode(self) -> bytes:
return self.to_tlv() return self.to_tlv()
class TL0_DataObject(DataObject): class TL0_DataObject(DataObject):
"""Data Object that has Tag, Len=0 and no Value part.""" """Data Object that has Tag, Len=0 and no Value part."""
def __init__(self, name: str, desc: str, tag: int, val=None): def __init__(self, name: str, desc: str, tag: int, val=None):
super().__init__(name, desc, tag) super().__init__(name, desc, tag)
self.val = val self.val = val
@@ -1289,6 +1375,7 @@ class DataObjectCollection:
"""A DataObjectCollection consits of multiple Data Objects identified by their tags. """A DataObjectCollection consits of multiple Data Objects identified by their tags.
A given encoded DO may contain any of them in any order, and may contain multiple instances A given encoded DO may contain any of them in any order, and may contain multiple instances
of each DO.""" of each DO."""
def __init__(self, name: str, desc: str = None, members=None): def __init__(self, name: str, desc: str = None, members=None):
self.name = name self.name = name
self.desc = desc self.desc = desc
@@ -1352,10 +1439,12 @@ class DataObjectCollection:
res.append(obj.to_tlv()) res.append(obj.to_tlv())
return res return res
class DataObjectChoice(DataObjectCollection): class DataObjectChoice(DataObjectCollection):
"""One Data Object from within a choice, identified by its tag. """One Data Object from within a choice, identified by its tag.
This means that exactly one member of the choice must occur, and which one occurs depends This means that exactly one member of the choice must occur, and which one occurs depends
on the tag.""" on the tag."""
def __add__(self, other): def __add__(self, other):
"""We overload the add operator here to avoid inheriting it from DataObjecCollection.""" """We overload the add operator here to avoid inheriting it from DataObjecCollection."""
raise TypeError raise TypeError
@@ -1395,11 +1484,13 @@ class DataObjectChoice(DataObjectCollection):
obj = self.members_by_name(decoded[0]) obj = self.members_by_name(decoded[0])
return obj.to_tlv() return obj.to_tlv()
class DataObjectSequence: class DataObjectSequence:
"""A sequence of DataObjects or DataObjectChoices. This allows us to express a certain """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain
ordered sequence of DOs or choices of DOs that have to appear as per the specification. ordered sequence of DOs or choices of DOs that have to appear as per the specification.
By wrapping them into this formal DataObjectSequence, we can offer convenience methods By wrapping them into this formal DataObjectSequence, we can offer convenience methods
for encoding or decoding an entire sequence.""" for encoding or decoding an entire sequence."""
def __init__(self, name: str, desc: str = None, sequence=None): def __init__(self, name: str, desc: str = None, sequence=None):
self.sequence = sequence or [] self.sequence = sequence or []
self.name = name self.name = name
@@ -1469,8 +1560,10 @@ class DataObjectSequence:
i += 1 i += 1
return encoded return encoded
class CardCommand: class CardCommand:
"""A single card command / instruction.""" """A single card command / instruction."""
def __init__(self, name, ins, cla_list=None, desc=None): def __init__(self, name, ins, cla_list=None, desc=None):
self.name = name self.name = name
self.ins = ins self.ins = ins
@@ -1503,6 +1596,7 @@ class CardCommand:
class CardCommandSet: class CardCommandSet:
"""A set of card instructions, typically specified within one spec.""" """A set of card instructions, typically specified within one spec."""
def __init__(self, name, cmds=[]): def __init__(self, name, cmds=[]):
self.name = name self.name = name
self.cmds = {c.ins: c for c in cmds} self.cmds = {c.ins: c for c in cmds}
@@ -1523,7 +1617,8 @@ class CardCommandSet:
for c in other.cmds.keys(): for c in other.cmds.keys():
self.cmds[c] = other.cmds[c] self.cmds[c] = other.cmds[c]
else: else:
raise ValueError('%s: Unsupported type to add operator: %s' % (self, other)) raise ValueError(
'%s: Unsupported type to add operator: %s' % (self, other))
def lookup(self, ins, cla=None): def lookup(self, ins, cla=None):
"""look-up the command within the CommandSet.""" """look-up the command within the CommandSet."""
@@ -1535,6 +1630,7 @@ class CardCommandSet:
return None return None
return cmd return cmd
def all_subclasses(cls) -> set: def all_subclasses(cls) -> set:
"""Recursively get all subclasses of a specified class""" """Recursively get all subclasses of a specified class"""
return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)]) return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)])