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.utils import *
def parse_options():
parser = OptionParser(usage="usage: %prog [options]")
@ -190,7 +191,6 @@ def parse_options():
parser.add_option("--read-csv", dest="read_csv", metavar="FILE",
help="Read parameters from CSV file rather than command line")
parser.add_option("--write-csv", dest="write_csv", metavar="FILE",
help="Append generated parameters in CSV file",
)
@ -215,12 +215,14 @@ def parse_options():
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):
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:
parser.error("CSV mode requires a CSV input file")
elif options.source == 'cmdline':
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:
parser.error("Only `cmdline' and `csv' sources supported")
@ -232,7 +234,8 @@ def parse_options():
if (options.batch_mode):
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:
parser.error("Extraneous arguments")
@ -246,15 +249,19 @@ def _digits(secret, usage, len, num):
d = ''.join(['%02d' % x for x in s.digest()])
return d[0:len]
def _mcc_mnc_digits(mcc, mnc):
return '%s%s' % (mcc, mnc)
def _cc_digits(cc):
return ('%03d' if cc > 100 else '%02d') % cc
def _isnum(s, l=-1):
return s.isdigit() and ((l == -1) or (len(s) == l))
def _ishex(s, l=-1):
hc = '0123456789abcdef'
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):
if i == 39:
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:
m = sum_
e = i
@ -292,6 +300,7 @@ def _dbi_binary_quote(s):
return ''.join(out)
def gen_parameters(opts):
"""Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the
options given by the user"""
@ -331,7 +340,8 @@ def gen_parameters(opts):
'Start with \'+\' for international numbers.')
if len(msisdn) > 10 * 2:
# 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 :(
if opts.iccid is not None:
@ -406,7 +416,8 @@ def gen_parameters(opts):
else:
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 = (
'e1' + # Parameters indicator
@ -456,7 +467,8 @@ def gen_parameters(opts):
epdg_mcc = opts.epdgSelection[:3]
epdg_mnc = opts.epdgSelection[3:]
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 {
@ -511,6 +523,7 @@ def write_params_csv(opts, params):
cw.writerow([params[x] for x in row])
f.close()
def _read_params_csv(opts, iccid=None, imsi=None):
import csv
f = open(opts.read_csv, 'r')
@ -539,6 +552,7 @@ def _read_params_csv(opts, iccid=None, imsi=None):
f.close()
return None
def read_params_csv(opts, imsi=None, iccid=None):
row = _read_params_csv(opts, iccid=iccid, imsi=imsi)
if row is not None:
@ -565,9 +579,11 @@ def read_params_csv(opts, imsi=None, iccid=None):
try:
try_encode = h2b(pin_adm)
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:
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
@ -597,12 +613,14 @@ def write_params_hlr(opts, params):
'(subscriber_id, algorithm_id, a3a8_ki)' +
'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.close()
def write_parameters(opts, params):
write_params_csv(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_INCOMPATIBLE = ['iccid', 'imsi', 'ki']
def init_batch(opts):
# Need to do something ?
if not opts.batch_mode:

View File

@ -45,6 +45,7 @@ option_parser = argparse.ArgumentParser(prog='pySim-read',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
argparse_add_reader_args(option_parser)
def select_app(adf: str, card: SimCard):
"""Select application by its AID"""
sw = 0
@ -63,6 +64,7 @@ def select_app(adf:str, card:SimCard):
return sw
if __name__ == '__main__':
# Parse options
@ -270,7 +272,8 @@ if __name__ == '__main__':
if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']):
(res, sw) = usim_card.read_epdgid()
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:
print("ePDGId: Can't read, response code = %s" % (sw,))
except Exception as e:
@ -297,7 +300,8 @@ if __name__ == '__main__':
try:
if isim_card.file_exists(EF_ISIM_ADF_map['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:
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']):
(res, sw) = isim_card.read_domain()
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:
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:
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']):
(res, sw) = isim_card.read_impi()
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:
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:
print("IMS private user identity: Can't read file -- " + str(e))
@ -327,7 +335,8 @@ if __name__ == '__main__':
try:
if isim_card.file_exists(EF_ISIM_ADF_map['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:
print("IMS public user identity: Can't read file -- " + str(e))
@ -335,7 +344,8 @@ if __name__ == '__main__':
try:
if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']):
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:
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
def init_card(sl):
"""
Detect card in reader and setup card profile and runtime state. This
@ -117,8 +118,10 @@ def init_card(sl):
return rs, card
class PysimApp(cmd2.Cmd):
CUSTOM_CATEGORY = 'pySim Commands'
def __init__(self, card, rs, sl, ch, script=None):
super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False,
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',
onchange_cb=self._onchange_conserve_write))
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.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card',
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
# needed to operate on cards.
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.register_command_set(Iso7816Commands())
self.register_command_set(PySimCommands())
@ -211,7 +216,8 @@ class PysimApp(cmd2.Cmd):
def update_prompt(self):
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))
else:
self.prompt = 'pySIM-shell (no card)> '
@ -234,10 +240,12 @@ class PysimApp(cmd2.Cmd):
class InterceptStderr(list):
def __init__(self):
self._stderr_backup = sys.stderr
def __enter__(self):
self._stringio_stderr = StringIO()
sys.stderr = self._stringio_stderr
return self
def __exit__(self, *args):
self.stderr = self._stringio_stderr.getvalue().strip()
del self._stringio_stderr
@ -305,7 +313,8 @@ class PysimApp(cmd2.Cmd):
return -1
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',
action='store_true')
bulk_script_parser.add_argument('--tries', type=int, default=2,
@ -350,13 +359,14 @@ class PysimApp(cmd2.Cmd):
if rc == 0:
success_count = success_count + 1
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
else:
fail_count = fail_count + 1
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
# "done" bin.
@ -391,7 +401,8 @@ class PysimApp(cmd2.Cmd):
self.poutput("")
fail_count = fail_count + 1
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
@ -404,16 +415,21 @@ class PysimApp(cmd2.Cmd):
"""Echo (print) a string on the console"""
self.poutput(opts.string)
@with_default_category('pySim Commands')
class PySimCommands(CommandSet):
def __init__(self):
super().__init__()
dir_parser = argparse.ArgumentParser()
dir_parser.add_argument('--fids', help='Show file identifiers', action='store_true')
dir_parser.add_argument('--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')
dir_parser.add_argument(
'--fids', help='Show file identifiers', action='store_true')
dir_parser.add_argument(
'--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)
def do_dir(self, opts):
@ -430,8 +446,10 @@ class PySimCommands(CommandSet):
flags += ['ANAMES', 'AIDS']
else:
flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES']
selectables = list(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)
selectables = list(
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)
self._cmd.poutput('/'.join(path_list))
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):
"""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:
if not action:
output_str = " " * indent + str(f) + (" " * 250)
@ -461,7 +480,8 @@ class PySimCommands(CommandSet):
skip_df = True
df = self._cmd.rs.selected_file
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:
context['DF_SKIP'] += 1
context['DF_SKIP_REASON'].append(df_skip_reason_str)
@ -492,7 +512,8 @@ class PySimCommands(CommandSet):
df = self._cmd.rs.selected_file
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_fid = df.fully_qualified_path(False)
@ -500,10 +521,12 @@ class PySimCommands(CommandSet):
file_str = '/'.join(df_path_list) + "/" + str(filename)
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:
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']
structure = fd['structure']
@ -522,7 +545,8 @@ class PySimCommands(CommandSet):
num_of_rec = fd['num_of_rec']
for r in range(1, num_of_rec + 1):
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
# first record that cannot be read.
else:
@ -537,7 +561,8 @@ class PySimCommands(CommandSet):
# Some other problem occurred
else:
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
elif structure == 'ber_tlv':
tags = self._cmd.rs.retrieve_tags()
@ -546,9 +571,11 @@ class PySimCommands(CommandSet):
(tag, l, val, remainer) = bertlv_parse_one(h2b(result[0]))
self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val)))
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:
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)
context['ERR'] += 1
context['BAD'].append(bad_file_str)
@ -562,12 +589,14 @@ class PySimCommands(CommandSet):
self._cmd.poutput("#")
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)
def do_export(self, opts):
"""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:
self.export(opts.filename, context)
else:
@ -580,16 +609,20 @@ class PySimCommands(CommandSet):
for b in context['BAD']:
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']:
self._cmd.poutput("# " + b)
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']:
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']:
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):
"""Reset the Card."""
@ -612,18 +645,22 @@ class PySimCommands(CommandSet):
pin_adm = sanitize_pin_adm(arg)
else:
# 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)
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:
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:
self._cmd.card.verify_adm(h2b(pin_adm))
else:
raise ValueError("error: cannot authenticate, no adm-pin!")
@with_default_category('ISO7816 Commands')
class Iso7816Commands(CommandSet):
def __init__(self):
@ -633,8 +670,10 @@ class Iso7816Commands(CommandSet):
"""SELECT a File (ADF/DF/EF)"""
if len(opts.arg_list) == 0:
path_list = self._cmd.rs.selected_file.fully_qualified_path(True)
path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(False)
self._cmd.poutput("currently selected file: " + '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(
False)
self._cmd.poutput("currently selected file: " +
'/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")")
return
path = opts.arg_list[0]
@ -654,17 +693,22 @@ class Iso7816Commands(CommandSet):
if str(code).upper() not in auto:
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)
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:
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
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('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
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(
'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)
def do_verify_chv(self, opts):
@ -674,34 +718,44 @@ class Iso7816Commands(CommandSet):
self._cmd.poutput("CHV verification successful")
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('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')
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(
'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)
def do_unblock_chv(self, opts):
"""Unblock PIN code using specified PUK code"""
new_pin = self.get_code(opts.new_pin_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")
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('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')
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(
'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)
def do_change_chv(self, opts):
"""Change PIN code to a new PIN code"""
new_pin = self.get_code(opts.new_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")
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('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
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(
'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)
def do_disable_chv(self, opts):
@ -711,8 +765,10 @@ class Iso7816Commands(CommandSet):
self._cmd.poutput("CHV disable successful")
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('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source')
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(
'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)
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)
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)
def do_open_channel(self, opts):
"""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.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)
def do_close_channel(self, opts):
"""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):
"""Perform the STATUS command."""
@ -768,7 +828,8 @@ class Iso7816Commands(CommandSet):
"""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,
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',
@ -778,7 +839,8 @@ argparse_add_reader_args(option_parser)
global_group = option_parser.add_argument_group('General Options')
global_group.add_argument('--script', metavar='PATH', default=None,
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",
help="Use automatic card handling machine")
@ -834,7 +896,8 @@ if __name__ == '__main__':
traceback.print_exc()
print("---------------------8<---------------------")
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("")
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)
class AidRefDO(BER_TLV_IE, tag=0x4f):
# SEID v1.1 Table 6-3
_construct = HexAdapter(GreedyBytes)
class AidRefEmptyDO(BER_TLV_IE, tag=0xc0):
# SEID v1.1 Table 6-3
pass
class DevAppIdRefDO(BER_TLV_IE, tag=0xc1):
# SEID v1.1 Table 6-4
_construct = HexAdapter(GreedyBytes)
class PkgRefDO(BER_TLV_IE, tag=0xca):
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
_construct = Struct('package_name_string'/GreedyString("ascii"))
class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO, AidRefEmptyDO, DevAppIdRefDO, PkgRefDO]):
# SEID v1.1 Table 6-5
pass
class ApduArDO(BER_TLV_IE, tag=0xd0):
# SEID v1.1 Table 6-8
def _from_bytes(self, do: bytes):
@ -76,6 +82,7 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
'mask': b2h(do[offset+4:offset+8])}
self.decoded = res
return res
def _to_bytes(self):
if 'generic_access_rule' in self.decoded:
if self.decoded['generic_access_rule'] == 'never':
@ -99,94 +106,118 @@ class ApduArDO(BER_TLV_IE, tag=0xd0):
res += header_b + mask_b
return res
class NfcArDO(BER_TLV_IE, tag=0xd1):
# 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):
# Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc
_construct = Struct('permissions'/HexAdapter(Bytes(8)))
class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]):
# SEID v1.1 Table 6-7
pass
class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]):
# SEID v1.1 Table 6-6
pass
class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]):
# SEID v1.1 Table 4-2
pass
class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]):
# SEID v1.1 Table 4-3
pass
class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20):
# SEID v1.1 Table 4-4
_construct = Struct('refresh_tag'/HexAdapter(Bytes(8)))
class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6):
# SEID v1.1 Table 6-12
_construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub)
class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]):
# SEID v1.1 Table 6-10
pass
class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]):
# SEID v1.1 Table 5-14
pass
class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]):
# SEID v1.1 Table 6-11
pass
class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]):
# SEID v1.1 Table 4-5
pass
class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]):
# SEID v1.1 Table 5-2
pass
class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]):
# SEID v1.1 Table 5-4
pass
class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2):
# SEID V1.1 Table 5-6
pass
class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-7
pass
class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-8
pass
class CommandGetAll(BER_TLV_IE, tag=0xf4):
# SEID v1.1 Table 5-9
pass
class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6):
# SEID v1.1 Table 5-10
pass
class CommandGetNext(BER_TLV_IE, tag=0xf5):
# SEID v1.1 Table 5-11
pass
class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8):
# SEID v1.1 Table 5-12
pass
class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]):
# SEID v1.1 Table 5-13
pass
class BlockDO(BER_TLV_IE, tag=0xe7):
# SEID v1.1 Table 6-13
_construct = Struct('offset'/Int16ub, 'length'/Int8ub)
@ -197,11 +228,15 @@ class GetCommandDoCollection(TLV_IE_Collection, nested=[RefDO, DeviceConfigDO]):
pass
# SEID v1.1 Table 4-2
class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO,
ResponseRefreshTagDO, ResponseAramConfigDO]):
pass
# SEID v1.1 Table 5-1
class StoreCommandDoCollection(TLV_IE_Collection,
nested=[BlockDO, CommandStoreRefArDO, CommandDelete,
CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO,
@ -215,6 +250,7 @@ class StoreResponseDoCollection(TLV_IE_Collection,
nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]):
pass
class ADF_ARAM(CardADF):
def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None,
desc='ARA-M Application'):
@ -259,7 +295,8 @@ class ADF_ARAM(CardADF):
@staticmethod
def get_config(tp, v_major=0, v_minor=0, v_patch=1):
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)
@with_default_category('Application-Specific Commands')
@ -281,20 +318,30 @@ class ADF_ARAM(CardADF):
store_ref_ar_do_parse = argparse.ArgumentParser()
# 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.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-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)')
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-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
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-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)')
apdu_grp.add_argument(
'--apdu-never', action='store_true', help='APDU access is not allowed')
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.add_argument('--nfc-always', action='store_true', help='NFC event access is allowed')
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)')
nfc_grp.add_argument('--nfc-always', action='store_true',
help='NFC event access is allowed')
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)
def do_aram_store_ref_ar_do(self, opts):
@ -359,6 +406,7 @@ sw_aram = {
}
}
class CardApplicationARAM(CardApplication):
def __init__(self):
super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram)

View File

@ -30,6 +30,7 @@ import subprocess
import sys
import yaml
class CardHandlerBase:
"""Abstract base class representing a mechanism for card insertion/removal."""
@ -116,7 +117,8 @@ class CardHandlerAuto(CardHandlerBase):
def __exec_cmd(self, 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()
rc = proc.returncode

View File

@ -35,10 +35,12 @@ import csv
card_key_providers = [] # type: List['CardKeyProvider']
class CardKeyProvider(abc.ABC):
"""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
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'
"""
class CardKeyProviderCsv(CardKeyProvider):
"""Card key provider implementation that allows to query against a specified CSV file"""
csv_file = None
@ -101,7 +104,8 @@ class CardKeyProviderCsv(CardKeyProvider):
self.csv_file.seek(0)
cr = csv.DictReader(self.csv_file)
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]
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:
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)
if 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:
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)
if result:
return result

View File

@ -32,6 +32,7 @@ from pySim.utils import *
from smartcard.util import toBytes
from pytlv.TLV import *
def format_addr(addr: str, addr_type: str) -> str:
"""
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)
return res
class SimCard(object):
name = 'SIM'
@ -126,7 +128,8 @@ class SimCard(object):
size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc)
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
def read_oplmn_act(self):
@ -142,7 +145,8 @@ class SimCard(object):
size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc)
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
def read_plmn_act(self):
@ -158,14 +162,16 @@ class SimCard(object):
size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc)
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
def update_plmnsel(self, mcc, mnc):
data = self._scc.read_binary(EF['PLMNsel'], length=None, offset=0)
size = len(data[0]) // 2
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
def update_smsp(self, smsp):
@ -193,7 +199,8 @@ class SimCard(object):
ad = EF_AD()
# 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)
# perform updates
@ -321,7 +328,8 @@ class SimCard(object):
def erase_record(self, ef, rec_no):
"""Erase the contents of a single record"""
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):
"""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)"""
return (self._scc.cla_byte, self._scc.sel_ctrl)
class UsimCard(SimCard):
name = 'USIM'
@ -347,7 +356,8 @@ class UsimCard(SimCard):
return (None, sw)
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
ehplmn = enc_plmn(mcc, mnc)
data, sw = self._scc.update_binary(EF_USIM_ADF_map['EHPLMN'], ehplmn)
@ -370,7 +380,8 @@ class UsimCard(SimCard):
if len(epdgid) > 0:
addr_type = get_addr_type(epdgid)
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)
else:
epdgid_tlv = rpad('ff', size)
@ -386,13 +397,16 @@ class UsimCard(SimCard):
return (None, sw)
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):
# Reset contents
# 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':
(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
def read_ust(self):
@ -407,9 +421,11 @@ class UsimCard(SimCard):
(res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST'])
if sw == '9000':
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
class IsimCard(SimCard):
name = 'ISIM'
@ -429,29 +445,33 @@ class IsimCard(SimCard):
addr = None
addr_type = None
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:
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
def update_pcscf(self, pcscf):
if len(pcscf) > 0:
addr_type = get_addr_type(pcscf)
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))
else:
# Just the tag value
content = '80'
rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['PCSCF'])
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
def read_domain(self):
(res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['DOMAIN'])
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)
content = h2s(res[4:4+(length*2)])
return (content, sw)
@ -472,13 +492,14 @@ class IsimCard(SimCard):
content = tlv.build({'80': hex_str})
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
def read_impi(self):
(res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IMPI'])
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)
content = h2s(res[4:4+(length*2)])
return (content, sw)
@ -494,7 +515,8 @@ class IsimCard(SimCard):
content = tlv.build({'80': hex_str})
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
def read_impu(self):
@ -503,12 +525,14 @@ class IsimCard(SimCard):
for i in range(0, rec_cnt):
(res, sw) = self._scc.read_record(EF_ISIM_ADF_map['IMPU'], i + 1)
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)
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:
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
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'])
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
def read_iari(self):
rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['UICCIARI'])
uiari_recs = ""
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':
# 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)
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:
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
class MagicSimBase(abc.ABC, SimCard):
"""
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 )
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 )
@ -830,6 +860,7 @@ class SysmoSIMgr1(GrcardSim):
return None
return None
class SysmoUSIMgr1(UsimCard):
"""
sysmocom sysmoUSIM-GR1
@ -845,7 +876,8 @@ class SysmoUSIMgr1(UsimCard):
# 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])
# 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
par = (p['ki'] + # 16b K
@ -956,7 +988,8 @@ class SysmoUSIMSJS1(UsimCard):
def verify_adm(self, key):
# authenticate as ADM using default key (written on the card..)
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)
return sw
@ -1027,7 +1060,8 @@ class SysmoUSIMSJS1(UsimCard):
# EF.SMSP
if p.get('smsp'):
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
# TODO: Alpha Identifier (currently 'ff'O * 20)
@ -1069,7 +1103,6 @@ class FairwavesSIM(UsimCard):
self._adm_chv_num = 0x11
self._adm2_chv_num = 0x12
@classmethod
def autodetect(kls, scc):
try:
@ -1080,7 +1113,6 @@ class FairwavesSIM(UsimCard):
return None
return None
def verify_adm2(self, key):
'''
Authenticate with ADM2 key.
@ -1093,7 +1125,6 @@ class FairwavesSIM(UsimCard):
(res, sw) = self._scc.verify_chv(self._adm2_chv_num, key)
return sw
def read_ki(self):
"""
Read Ki in proprietary file.
@ -1102,7 +1133,6 @@ class FairwavesSIM(UsimCard):
"""
return self._scc.read_binary(self._EF['Ki'])
def update_ki(self, ki):
"""
Set Ki in proprietary file.
@ -1112,7 +1142,6 @@ class FairwavesSIM(UsimCard):
data, sw = self._scc.update_binary(self._EF['Ki'], ki)
return sw
def read_op_opc(self):
"""
Read Ki in proprietary file.
@ -1123,7 +1152,6 @@ class FairwavesSIM(UsimCard):
type = 'OP' if ef[0:2] == '00' else 'OPC'
return ((type, ef[2:]), sw)
def update_op(self, op):
"""
Set OP in proprietary file.
@ -1134,7 +1162,6 @@ class FairwavesSIM(UsimCard):
data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
return sw
def update_opc(self, opc):
"""
Set OPC in proprietary file.
@ -1166,7 +1193,8 @@ class FairwavesSIM(UsimCard):
def _program(self, p):
# authenticate as ADM1
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']))
# TODO: Set operator name
@ -1196,6 +1224,7 @@ class FairwavesSIM(UsimCard):
if sw != '9000':
print("Programming ACC failed with code %s" % sw)
class OpenCellsSim(SimCard):
"""
OpenCellsSim
@ -1208,7 +1237,6 @@ class OpenCellsSim(SimCard):
super(OpenCellsSim, self).__init__(ssc)
self._adm_chv_num = 0x0A
@classmethod
def autodetect(kls, scc):
try:
@ -1219,10 +1247,10 @@ class OpenCellsSim(SimCard):
return None
return None
def program(self, p):
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']))
# select MF
@ -1245,6 +1273,7 @@ class OpenCellsSim(SimCard):
# write EF.IMSI
data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
class WavemobileSim(UsimCard):
"""
WavemobileSim
@ -1271,23 +1300,27 @@ class WavemobileSim(UsimCard):
def program(self, p):
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']))
# EF.ICCID
# TODO: Add programming of the 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)
# TODO: Add programming of 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)
# TODO: Add programming of 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
if p.get('smsp'):
@ -1374,7 +1407,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
def verify_adm(self, key):
# authenticate as ADM using default key (written on the card..)
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)
return sw
@ -1386,7 +1420,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
# license management, so the ICCID must be kept at its factory
# setting!
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
self._scc.select_path(['7f20'])
@ -1436,7 +1471,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
# EF.SMSP
if p.get('smsp'):
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
# TODO: Alpha Identifier (currently 'ff'O * 20)
@ -1447,7 +1483,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
content = 'ff' * 20 + msisdn
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
if p.get('acc'):
@ -1484,7 +1521,6 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
if sw != '9000':
print("Programming P-CSCF failed with code %s" % sw)
# update EF.DOMAIN in ADF.ISIM
if self.file_exists(EF_ISIM_ADF_map['DOMAIN']):
if p.get('ims_hdomain'):
@ -1493,7 +1529,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
sw = self.update_domain()
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
# TODO: Validate IMPI input
@ -1544,13 +1581,13 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
if self.file_exists(EF_USIM_ADF_map['ePDGSelection']):
if p.get('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:
sw = self.update_ePDGSelection("", "")
if sw != '9000':
print("Programming ePDGSelection failed with code %s" % sw)
# After successfully programming EF.ePDGId and EF.ePDGSelection,
# Set service 106 and 107 as available in EF.UST
# Disable service 95, 99, 115 if ISIM application is present
@ -1581,6 +1618,7 @@ _cards_classes = [ FakeMagicSim, SuperSim, MagicSim, GrcardSim,
SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1,
FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2]
def card_detect(ctype, scc):
# Detect type if needed
card = None

View File

@ -24,34 +24,48 @@ from construct import *
# Tag values as per TS 101 220 Table 7.23
# TS 102 223 Section 8.1
class Address(COMPR_TLV_IE, tag=0x06):
_construct = Struct('ton_npi'/Int8ub,
'call_number'/BcdAdapter(Bytes(this._.total_len-1)))
# TS 102 223 Section 8.2
class AlphaIdentifier(COMPR_TLV_IE, tag=0x05):
# FIXME: like EF.ADN
pass
# TS 102 223 Section 8.3
class Subaddress(COMPR_TLV_IE, tag=0x08):
pass
# TS 102 223 Section 8.4
class CapabilityConfigParams(COMPR_TLV_IE, tag=0x07):
pass
# TS 31.111 Section 8.5
class CBSPage(COMPR_TLV_IE, tag=0x0C):
pass
# TS 102 223 Section 8.6
class CommandDetails(COMPR_TLV_IE, tag=0x01):
_construct = Struct('command_number'/Int8ub,
'type_of_command'/Int8ub,
'command_qualifier'/Int8ub)
# TS 102 223 Section 8.7
class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
DEV_IDS = bidict({
0x01: 'keypad',
@ -91,6 +105,7 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82):
0x82: 'terminal',
0x83: 'network',
})
def _from_bytes(self, do: bytes):
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])
# TS 102 223 Section 8.8
class Duration(COMPR_TLV_IE, tag=0x04):
_construct = Struct('time_unit'/Int8ub,
'time_interval'/Int8ub)
# TS 102 223 Section 8.9
class Item(COMPR_TLV_IE, tag=0x0f):
_construct = Struct('identifier'/Int8ub,
'text_string'/GsmStringAdapter(GreedyBytes))
# TS 102 223 Section 8.10
class ItemIdentifier(COMPR_TLV_IE, tag=0x10):
_construct = Struct('identifier'/Int8ub)
# TS 102 223 Section 8.11
class ResponseLength(COMPR_TLV_IE, tag=0x11):
_construct = Struct('minimum_length'/Int8ub,
'maximum_length'/Int8ub)
# TS 102 223 Section 8.12
class Result(COMPR_TLV_IE, tag=0x03):
_construct = Struct('general_result'/Int8ub,
'additional_information'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.13 + TS 31.111 Section 8.13
class SMS_TPDU(COMPR_TLV_IE, tag=0x8B):
_construct = Struct('tpdu'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.15
class TextString(COMPR_TLV_IE, tag=0x0d):
_construct = Struct('dcs'/Int8ub,
'text_string'/HexAdapter(GreedyBytes))
# TS 102 223 Section 8.16
class Tone(COMPR_TLV_IE, tag=0x0e):
_construct = Struct('tone'/Int8ub)
# TS 31 111 Section 8.17
class USSDString(COMPR_TLV_IE, tag=0x0a):
_construct = Struct('dcs'/Int8ub,
'ussd_string'/HexAdapter(GreedyBytes))
# TS 101 220 Table 7.17
class ProactiveCommand(BER_TLV_IE, tag=0xD0):
pass
# TS 101 220 Table 7.17 + 31.111 7.1.1.2
class SMSPPDownload(BER_TLV_IE, tag=0xD1,
nested=[DeviceIdentities, Address, SMS_TPDU]):
pass
# TS 101 220 Table 7.17 + 31.111 7.1.1.3
class SMSCBDownload(BER_TLV_IE, tag=0xD2,
nested=[DeviceIdentities, CBSPage]):
pass
class USSDDownload(BER_TLV_IE, tag=0xD9,
nested=[DeviceIdentities, USSDString]):
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.exceptions import SwMatchError
class SimCardCommands(object):
def __init__(self, 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,
# DF or ADF
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!
fcp = fcp.lower()
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
# 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:
dir_list = [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))
if sw != '9000':
return rv
@ -162,11 +166,13 @@ class SimCardCommands(object):
chunk_offset = 0
while chunk_offset < length:
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:
data, sw = self._tp.send_apdu_checksw(pdu)
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
chunk_offset += chunk_len
return total_data, sw
@ -194,11 +200,13 @@ class SimCardCommands(object):
while chunk_offset < data_length:
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
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:
chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu)
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))
total_data += data
chunk_offset += chunk_len
@ -216,7 +224,8 @@ class SimCardCommands(object):
"""
res = self.read_binary(ef, len(data) // 2, offset)
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):
"""Execute READ RECORD.
@ -253,7 +262,8 @@ class SimCardCommands(object):
# exceed we throw an exception.
rec_length = self.__record_len(res)
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):
data = rpad(data, rec_length * 2)
@ -280,7 +290,8 @@ class SimCardCommands(object):
"""
res = self.read_record(ef, rec_no)
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):
"""Determine the record size of given file.
@ -400,7 +411,8 @@ class SimCardCommands(object):
# 3GPP TS 31.102 Section 7.1.2.1
AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(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)
# build parameters
cmd_data = {'rand': rand, 'autn': autn}
@ -408,7 +420,8 @@ class SimCardCommands(object):
p2 = '81'
elif context == 'gsm':
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:
ret = {'synchronisation_failure': data}
else:
@ -464,7 +477,8 @@ class SimCardCommands(object):
code : chv code as hex string
"""
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)
return (data, sw)
@ -477,7 +491,8 @@ class SimCardCommands(object):
pin_code : new chv code as hex string
"""
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)
return (data, sw)
@ -490,7 +505,8 @@ class SimCardCommands(object):
new_pin_code : new chv code as hex string
"""
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)
return (data, sw)
@ -503,7 +519,8 @@ class SimCardCommands(object):
new_pin_code : new chv code as hex string
"""
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)
return (data, sw)
@ -515,7 +532,8 @@ class SimCardCommands(object):
pin_code : chv code as hex string
"""
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)
return (data, sw)
@ -556,6 +574,7 @@ class SimCardCommands(object):
return '01%02x' % (secs // 60)
else:
return '00%02x' % secs
def decode_duration(enc: Hexstr) -> int:
time_unit = enc[:2]
length = h2i(enc[2:4])
@ -573,7 +592,8 @@ class SimCardCommands(object):
raise ValueError('Time unit must be 0x00..0x04')
min_dur_enc = encode_duration(min_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])
resume_token = data[4:]
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
from construct import *
from pySim.utils import b2h, h2b, swap_nibbles
@ -23,18 +25,24 @@ import gsm0338
class HexAdapter(Adapter):
"""convert a bytes() type to a string of hex nibbles."""
def _decode(self, obj, context, path):
return b2h(obj)
def _encode(self, obj, context, path):
return h2b(obj)
class BcdAdapter(Adapter):
"""convert a bytes() type to a string of BCD nibbles."""
def _decode(self, obj, context, path):
return swap_nibbles(b2h(obj))
def _encode(self, obj, context, path):
return h2b(swap_nibbles(obj))
class Rpad(Adapter):
"""
Encoder appends padding bytes (b'\\xff') up to target size.
@ -54,9 +62,11 @@ class Rpad(Adapter):
def _encode(self, obj, context, path):
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))
class GsmStringAdapter(Adapter):
"""Convert GSM 03.38 encoded bytes to a string."""
@ -71,6 +81,7 @@ class GsmStringAdapter(Adapter):
def _encode(self, obj, context, path):
return obj.encode(self.codec, self.err)
def filter_dict(d, exclude_prefix='_'):
"""filter the input dict to ensure no keys starting with 'exclude_prefix' remain."""
if not isinstance(d, dict):
@ -85,8 +96,6 @@ def filter_dict(d, exclude_prefix='_'):
res[key] = value
return res
from construct.lib.containers import Container, ListContainer
from construct.core import EnumIntegerString
def normalize_construct(c):
"""Convert a construct specific type to a related base type, mostly useful
@ -106,6 +115,7 @@ def normalize_construct(c):
r = c
return r
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()."""
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)
return normalize_construct(parsed)
# here we collect some shared / common definitions of data types
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
GreedyBytesRFU = Default(GreedyBytes, b'')
def BitsRFU(n=1):
'''
Field that packs Reserved for Future Use (RFU) bit(s)
@ -143,6 +155,7 @@ def BitsRFU(n=1):
'''
return Default(BitsInteger(n), __RFU_VALUE)
def BytesRFU(n=1):
'''
Field that packs Reserved for Future Use (RFU) byte(s)
@ -157,6 +170,7 @@ def BytesRFU(n=1):
'''
return Default(Bytes(n), __RFU_VALUE)
def GsmString(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/>.
#
class NoCardError(Exception):
"""No card was found in the reader."""
pass
class ProtocolError(Exception):
"""Some kind of protocol level error interfacing with the card."""
pass
class ReaderError(Exception):
"""Some kind of general error with the card reader."""
pass
class SwMatchError(Exception):
"""Raised when an operation specifies an expected SW but the actual SW from
the card doesn't match."""
def __init__(self, sw_actual: str, sw_expected: str, rs=None):
"""
Args:
@ -46,6 +51,7 @@ class SwMatchError(Exception):
self.sw_actual = sw_actual
self.sw_expected = sw_expected
self.rs = rs
def __str__(self):
if self.rs:
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.commands import SimCardCommands
class CardFile(object):
"""Base class for all objects in the smart card filesystem.
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 None
class CardDF(CardFile):
"""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 ignore_existing:
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):
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 ignore_existing:
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
child.parent = self
@ -307,6 +312,7 @@ class CardDF(CardFile):
class CardMF(CardDF):
"""MF (Master File) in the smart card filesystem"""
def __init__(self, **kwargs):
# can be overridden; use setdefault
kwargs.setdefault('fid', '3f00')
@ -353,7 +359,8 @@ class CardMF(CardDF):
if flags == [] or 'AIDS' in flags:
sels.update({x.aid: x for x in self.applications.values()})
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
def decode_select_response(self, data_hex: str) -> object:
@ -372,8 +379,10 @@ class CardMF(CardDF):
else:
return data_hex
class CardADF(CardDF):
"""ADF (Application Dedicated File) in the smart card filesystem"""
def __init__(self, aid: str, **kwargs):
super().__init__(**kwargs)
# reference to CardApplication may be set from CardApplication constructor
@ -395,6 +404,7 @@ class CardADF(CardDF):
class CardEF(CardFile):
"""EF (Entry File) in the smart card filesystem"""
def __init__(self, *, fid, **kwargs):
kwargs['fid'] = fid
super().__init__(**kwargs)
@ -414,7 +424,8 @@ class CardEF(CardFile):
"""
# global selectable names + those of the parent DF
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
@ -427,12 +438,16 @@ class TransparentEF(CardEF):
@with_default_category('Transparent EF Commands')
class ShellCommands(CommandSet):
"""Shell commands specific for transparent EFs."""
def __init__(self):
super().__init__()
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('--length', type=int, help='Number of bytes to read')
read_bin_parser.add_argument(
'--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)
def do_read_binary(self, opts):
"""Read binary data from a transparent EF"""
@ -442,6 +457,7 @@ class TransparentEF(CardEF):
read_bin_dec_parser = argparse.ArgumentParser()
read_bin_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_bin_dec_parser)
def do_read_binary_decoded(self, opts):
"""Read + decode data from a transparent EF"""
@ -449,8 +465,11 @@ class TransparentEF(CardEF):
self._cmd.poutput_json(data, opts.oneline)
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('data', help='Data bytes (hex format) to write')
upd_bin_parser.add_argument(
'--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)
def do_update_binary(self, opts):
"""Update (Write) data of a transparent EF"""
@ -459,15 +478,18 @@ class TransparentEF(CardEF):
self._cmd.poutput(data)
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,
help='JSON path to modify specific element of file only')
@cmd2.with_argparser(upd_bin_dec_parser)
def do_update_binary_decoded(self, opts):
"""Encode + Update (Write) data of a transparent EF"""
if opts.json_path:
(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:
data_json = json.loads(opts.data)
(data, sw) = self._cmd.rs.update_binary_dec(data_json)
@ -493,7 +515,6 @@ class TransparentEF(CardEF):
if data:
self._cmd.poutput_json(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
size={1, None}):
"""
@ -588,7 +609,8 @@ class TransparentEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data)
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:
"""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.from_dict(abstract_data)
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):
@ -627,12 +650,16 @@ class LinFixedEF(CardEF):
@with_default_category('Linear Fixed EF Commands')
class ShellCommands(CommandSet):
"""Shell commands specific for Linear Fixed EFs."""
def __init__(self):
super().__init__()
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('--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
read_rec_parser.add_argument(
'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)
def do_read_record(self, opts):
"""Read one or multiple records from a record-oriented EF"""
@ -646,9 +673,11 @@ class LinFixedEF(CardEF):
self._cmd.poutput("%03d %s" % (recnr, recstr))
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',
help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_rec_dec_parser)
def do_read_record_decoded(self, opts):
"""Read + decode a record from a record-oriented EF"""
@ -656,6 +685,7 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data, opts.oneline)
read_recs_parser = argparse.ArgumentParser()
@cmd2.with_argparser(read_recs_parser)
def do_read_records(self, opts):
"""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.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_recs_dec_parser)
def do_read_records_decoded(self, opts):
"""Read + decode all records from a record-oriented EF"""
@ -683,8 +714,11 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data_list, opts.oneline)
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('data', help='Data bytes (hex format) to write')
upd_rec_parser.add_argument(
'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)
def do_update_record(self, opts):
"""Update (write) data to a record-oriented EF"""
@ -693,24 +727,31 @@ class LinFixedEF(CardEF):
self._cmd.poutput(data)
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('data', help='Abstract data (JSON format) to write')
upd_rec_dec_parser.add_argument(
'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,
help='JSON path to modify specific element of record only')
@cmd2.with_argparser(upd_rec_dec_parser)
def do_update_record_decoded(self, opts):
"""Encode + Update (write) data to a record-oriented EF"""
if opts.json_path:
(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:
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:
self._cmd.poutput(data)
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)
def do_edit_record_decoded(self, opts):
"""Edit the JSON representation of one record in an editor."""
@ -727,11 +768,11 @@ class LinFixedEF(CardEF):
if edited_json == orig_json:
self._cmd.poutput("Data not modified, skipping write")
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:
self._cmd.poutput_json(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = 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.from_dict(abstract_data)
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:
"""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.from_dict(abstract_data)
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):
"""Cyclic EF (Entry File) in the smart card filesystem"""
# 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,
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):
"""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
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,
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.from_dict(abstract_data)
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:
"""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.from_dict(abstract_data)
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):
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]
def _encode_bin(self, abstract_data) -> bytes:
@ -1014,11 +1065,14 @@ class BerTlvEF(CardEF):
@with_default_category('BER-TLV EF Commands')
class ShellCommands(CommandSet):
"""Shell commands specific for BER-TLV EFs."""
def __init__(self):
super().__init__()
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)
def do_retrieve_data(self, opts):
"""Retrieve (Read) data from a BER-TLV EF"""
@ -1031,8 +1085,11 @@ class BerTlvEF(CardEF):
self._cmd.poutput(tags)
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('data', help='Data bytes (hex format) to write')
set_data_parser.add_argument(
'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)
def do_set_data(self, opts):
"""Set (Write) data for a given tag in a BER-TLV EF"""
@ -1041,7 +1098,9 @@ class BerTlvEF(CardEF):
self._cmd.poutput(data)
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)
def do_delete_data(self, opts):
"""Delete data for a given tag in a BER-TLV EF"""
@ -1049,7 +1108,6 @@ class BerTlvEF(CardEF):
if data:
self._cmd.poutput(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
size={1, None}):
"""
@ -1069,6 +1127,7 @@ class BerTlvEF(CardEF):
class RuntimeState(object):
"""Represent the runtime state of a session with a card."""
def __init__(self, card, profile: 'CardProfile'):
"""
Args:
@ -1082,7 +1141,8 @@ class RuntimeState(object):
# make sure the class and selection control bytes, which are specified
# 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
apps = self._match_applications()
@ -1195,7 +1255,8 @@ class RuntimeState(object):
"""Blindly try to select a file and automatically add a matching file
object if the file actually exists."""
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:
(data, sw) = self.card._scc.select_file(fid)
@ -1207,12 +1268,15 @@ class RuntimeState(object):
select_resp = self.selected_file.decode_select_response(data)
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:
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:
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 = f
@ -1408,9 +1472,9 @@ class RuntimeState(object):
cmd_app.unregister_command_set(c)
class FileData(object):
"""Represent the runtime, on-card data."""
def __init__(self, fdesc):
self.desc = fdesc
self.fcp = None
@ -1435,9 +1499,11 @@ def interpret_sw(sw_data:dict, sw:str):
return (class_str, descr)
return None
class CardApplication(object):
"""A card application is represented by an ADF (with contained hierarchy) and optionally
some SW definitions."""
def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
"""
Args:

View File

@ -43,6 +43,7 @@ import pySim.ts_51_011
# DF.EIRENE (FFFIS for GSM-R SIM Cards)
######################################################################
class FuncNTypeAdapter(Adapter):
def _decode(self, obj, context, path):
bcd = swap_nibbles(b2h(obj))
@ -50,19 +51,24 @@ class FuncNTypeAdapter(Adapter):
return {'functional_number': bcd[:-1],
'presentation_of_only_this_fn': last_digit & 4,
'permanent_fn': last_digit & 8}
def _encode(self, obj, context, path):
return 'FIXME'
class EF_FN(LinFixedEF):
"""Section 7.2"""
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)),
'list_number'/Int8ub)
class PlConfAdapter(Adapter):
"""Section 7.4.3"""
def _decode(self, obj, context, path):
num = int(obj) & 0x7
if num == 0:
@ -77,6 +83,7 @@ class PlConfAdapter(Adapter):
return 1
elif num == 5:
return 0
def _encode(self, obj, context, path):
if obj == 'None':
return 0
@ -92,8 +99,10 @@ class PlConfAdapter(Adapter):
elif obj == 0:
return 5
class PlCallAdapter(Adapter):
"""Section 7.4.12"""
def _decode(self, obj, context, path):
num = int(obj) & 0x7
if num == 0:
@ -112,6 +121,7 @@ class PlCallAdapter(Adapter):
return 'B'
elif num == 7:
return 'A'
def _encode(self, obj, context, path):
if obj == 'None':
return 0
@ -130,10 +140,14 @@ class PlCallAdapter(Adapter):
elif obj == 'A':
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):
"""Section 7.3"""
def __init__(self):
super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size={24, 24},
desc='Call Configuration of emergency calls Configuration')
@ -147,29 +161,39 @@ class EF_CallconfC(TransparentEF):
'shunting_emergency_gid'/Int8ub,
'imei'/BcdAdapter(Bytes(8)))
class EF_CallconfI(LinFixedEF):
"""Section 7.5"""
def __init__(self):
super().__init__(fid='6ff3', sfid=None, name='EF.CallconfI', rec_len={21, 21},
desc='Call Configuration of emergency calls Information')
self._construct = Struct('t_dur'/Int24ub,
't_relcalc'/Int32ub,
'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)),
'fnr'/BcdAdapter(Bytes(8)))
class EF_Shunting(TransparentEF):
"""Section 7.6"""
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,
'shunting_gid'/Bytes(7))
class EF_GsmrPLMN(LinFixedEF):
"""Section 7.7"""
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)),
'class_of_network'/BitStruct('supported'/FlagsEnum(BitsInteger(5), vbs=1, vgcs=2, emlpp=4, fn=8, eirene=16),
'preference'/BitsInteger(3)),
@ -177,34 +201,46 @@ class EF_GsmrPLMN(LinFixedEF):
'outgoing_ref_tbl'/HexAdapter(Bytes(2)),
'ic_table_ref'/HexAdapter(Bytes(1)))
class EF_IC(LinFixedEF):
"""Section 7.8"""
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,
'id_of_next_table'/HexAdapter(Bytes(2)),
'ic_decision_value'/BcdAdapter(Bytes(2)),
'network_string_table_index'/Int8ub)
class EF_NW(LinFixedEF):
"""Section 7.9"""
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)
class EF_Switching(LinFixedEF):
"""Section 8.4"""
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,
'id_of_next_table'/HexAdapter(Bytes(2)),
'decision_value'/BcdAdapter(Bytes(2)),
'string_table_index'/Int8ub)
class EF_Predefined(LinFixedEF):
"""Section 8.5"""
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 !?!
self._construct = Struct('next_table_type'/NextTableType,
'id_of_next_table'/HexAdapter(Bytes(2)),
@ -212,8 +248,10 @@ class EF_Predefined(LinFixedEF):
'string_table_index1'/Int8ub)
# TODO: predefined value n, ...
class EF_DialledVals(TransparentEF):
"""Section 8.6"""
def __init__(self, fid, name, desc):
super().__init__(fid=fid, sfid=None, name=name, desc=desc, size={4, 4})
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='6f8f', name='EF.SC', desc='Short 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='6f8a', name='EF.Call', desc='First digit of the group ID'),
EF_Predefined(fid='6f8b', name='EF.FctTeam', desc='Call Type 6 Team Type + Team member function'),
EF_Predefined(fid='6f92', name='EF.Controller', desc='Call Type 7 Controller function code'),
EF_Predefined(fid='6f8c', name='EF.Gateway', desc='Access to external networks'),
EF_DialledVals(fid='6f81', name='EF.5to8digits', desc='Call Type 2 User Identity Number length'),
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'),
EF_Predefined(fid='6f89', name='EF.Service',
desc='VGCS/VBS Service Code'),
EF_Predefined(fid='6f8a', name='EF.Call',
desc='First digit of the group ID'),
EF_Predefined(fid='6f8b', name='EF.FctTeam',
desc='Call Type 6 Team Type + Team member function'),
EF_Predefined(fid='6f92', name='EF.Controller',
desc='Call Type 7 Controller function code'),
EF_Predefined(fid='6f8c', name='EF.Gateway',
desc='Access to external networks'),
EF_DialledVals(fid='6f81', name='EF.5to8digits',
desc='Call Type 2 User Identity Number length'),
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)

View File

@ -24,38 +24,56 @@ from pySim.filesystem import *
from pySim.tlv import *
# Table 91 + Section 8.2.1.2
class ApplicationId(BER_TLV_IE, tag=0x4f):
_construct = GreedyBytes
# Table 91
class ApplicationLabel(BER_TLV_IE, tag=0x50):
_construct = GreedyBytes
# Table 91 + Section 5.3.1.2
class FileReference(BER_TLV_IE, tag=0x51):
_construct = GreedyBytes
# Table 91
class CommandApdu(BER_TLV_IE, tag=0x52):
_construct = GreedyBytes
# Table 91
class DiscretionaryData(BER_TLV_IE, tag=0x53):
_construct = GreedyBytes
# Table 91
class DiscretionaryTemplate(BER_TLV_IE, tag=0x73):
_construct = GreedyBytes
# Table 91 + RFC1738 / RFC2396
class URL(BER_TLV_IE, tag=0x5f50):
_construct = GreedyString('ascii')
# Table 91
class ApplicationRelatedDOSet(BER_TLV_IE, tag=0x61):
_construct = GreedyBytes
# Section 8.2.1.3 Application Template
class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationId, ApplicationLabel, FileReference,
CommandApdu, DiscretionaryData, DiscretionaryTemplate, URL,
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
# along with this program. If not, see <http://www.gnu.org/licenses/>.
def js_path_find(js_dict, js_path):
"""Find/Match a JSON path within a given JSON-serializable dict.
Args:
@ -35,6 +36,7 @@ def js_path_find(js_dict, js_path):
jsonpath_expr = jsonpath_ng.parse(js_path)
return jsonpath_expr.find(js_dict)
def js_path_modify(js_dict, js_path, new_val):
"""Find/Match a JSON path within a given JSON-serializable dict.
Args:
@ -45,4 +47,3 @@ def js_path_modify(js_dict, js_path, new_val):
jsonpath_expr = jsonpath_ng.parse(js_path)
jsonpath_expr.find(js_dict)
jsonpath_expr.update(js_dict, new_val)

View File

@ -27,6 +27,7 @@ from pySim.utils import all_subclasses
import abc
import operator
def _mf_select_test(scc: SimCardCommands, cla_byte: str, sel_ctrl: str) -> bool:
cla_byte_bak = scc.cla_byte
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
return rc
def match_uicc(scc: SimCardCommands) -> bool:
""" Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the
card is considered a UICC card.
"""
return _mf_select_test(scc, "00", "0004")
def match_sim(scc: SimCardCommands) -> bool:
""" 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
@ -58,6 +61,7 @@ def match_sim(scc:SimCardCommands) -> bool:
"""
return _mf_select_test(scc, "a0", "0000")
class CardProfile(object):
"""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

View File

@ -43,9 +43,11 @@ mac_length = {
1: 4
}
class EF_PIN(TransparentEF):
def __init__(self, fid, name):
super().__init__(fid, name=name, desc='%s PIN file' % name)
def _decode_bin(self, raw_bin_data):
u = unpack('!BBB8s', raw_bin_data[:11])
res = {'enabled': (True, False)[u[0] & 0x01],
@ -65,9 +67,11 @@ class EF_PIN(TransparentEF):
res['puk'] = u2[2].hex()
return res
class EF_MILENAGE_CFG(TransparentEF):
def __init__(self, fid='6f21', name='EF.MILENAGE_CFG', desc='Milenage connfiguration'):
super().__init__(fid, name=name, desc=desc)
def _decode_bin(self, 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],
@ -78,9 +82,11 @@ class EF_MILENAGE_CFG(TransparentEF):
'c5': u[9].hex(),
}
class EF_0348_KEY(LinFixedEF):
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})
def _decode_record_bin(self, raw_bin_data):
u = unpack('!BBB', raw_bin_data[0:3])
key_algo = (u[2] >> 6) & 1
@ -94,32 +100,40 @@ class EF_0348_KEY(LinFixedEF):
'key': raw_bin_data[3:key_length].hex()
}
class EF_0348_COUNT(LinFixedEF):
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})
def _decode_record_bin(self, raw_bin_data):
u = unpack('!BB5s', raw_bin_data)
return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2]}
class EF_SIM_AUTH_COUNTER(TransparentEF):
def __init__(self, fid='af24', name='EF.SIM_AUTH_COUNTER'):
super().__init__(fid, name=name, desc='Number of remaining RUN GSM ALGORITHM executions')
self._construct = Struct('num_run_gsm_algo_remain'/Int32ub)
class EF_GP_COUNT(LinFixedEF):
def __init__(self, fid='6f26', name='EF.GP_COUNT', desc='GP SCP02 Counters'):
super().__init__(fid, name=name, desc=desc, rec_len={5, 5})
def _decode_record_bin(self, 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]}
class EF_GP_DIV_DATA(LinFixedEF):
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})
def _decode_record_bin(self, 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()}
class EF_SIM_AUTH_KEY(TransparentEF):
def __init__(self, fid='6f20', name='EF.SIM_AUTH_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))
self._construct = Struct('cfg'/CfgByte,
'key'/Bytes(16),
'op'/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))
'op' /
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):
def __init__(self):
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):
return pySim.ts_102_221.CardProfileUICC.decode_select_response(resp_hex)
class EF_USIM_SQN(TransparentEF):
def __init__(self, fid='af30', name='EF.USIM_SQN'):
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,
'aus_concealed'/Bit, 'autn_concealed'/Bit)
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)))
class EF_USIM_AUTH_KEY(TransparentEF):
def __init__(self, fid='af20', name='EF.USIM_AUTH_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))
self._construct = Struct('cfg'/CfgByte,
'key'/Bytes(16),
'op'/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))
'op' /
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):
def __init__(self, fid='af22', name='EF.USIM_AUTH_KEY_2G'):
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))
self._construct = Struct('cfg'/CfgByte,
'key'/Bytes(16),
'op'/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))
'op' /
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):
def __init__(self, fid='af31', name='EF.GBA_SK'):
super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
self._construct = GreedyBytes
class EF_GBA_REC_LIST(TransparentEF):
def __init__(self, fid='af32', name='EF.GBA_REC_LIST'):
super().__init__(fid, name=name, desc='Secret key for GBA key derivation')
# integers representing record numbers in EF-GBANL
self._construct = GreedyRange(Int8ub)
class EF_GBA_INT_KEY(LinFixedEF):
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
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",
"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"]
@classmethod
def add_files(cls, rs: RuntimeState):
"""Add sysmocom SJA2 specific files to given RuntimeState."""

View File

@ -32,6 +32,7 @@ from pySim.exceptions import *
import inspect
import abc
class TlvMeta(abc.ABCMeta):
"""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
@ -54,6 +55,7 @@ class TlvMeta(abc.ABCMeta):
x.nested_collection_cls = cls
return x
class TlvCollectionMeta(abc.ABCMeta):
"""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
@ -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 an instance _construct variable, or
* via a derived class' _{to,from}_bytes() methods."""
def __init__(self):
self.encoded = None
self.decoded = None
@ -113,6 +116,7 @@ class Transcodable(abc.ABC):
def _from_bytes(self, do: bytes):
raise NotImplementedError
class IE(Transcodable, metaclass=TlvMeta):
# 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
@ -188,6 +192,7 @@ class IE(Transcodable, metaclass=TlvMeta):
class TLV_IE(IE):
"""Abstract base class for various TLV type Information Elements."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
@ -240,6 +245,7 @@ class TLV_IE(IE):
class BER_TLV_IE(TLV_IE):
"""TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
@ -264,6 +270,7 @@ class BER_TLV_IE(TLV_IE):
class COMPR_TLV_IE(TLV_IE):
"""TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.comprehension = False
@ -294,6 +301,7 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
of each DO."""
# this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
possible_nested = []
def __init__(self, desc=None, **kwargs):
self.desc = desc
#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/>.
#
class ApduTracer:
def trace_command(self, cmd):
pass
@ -105,7 +106,7 @@ class LinkBase(abc.ABC):
"""
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
# xx is the number of response bytes available.
# See also:
@ -185,11 +186,13 @@ class LinkBase(abc.ABC):
Returns:
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):
raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter)
return (rsp, sw)
def argparse_add_reader_args(arg_parser):
"""Add all reader related arguments to the given argparse.Argumentparser instance."""
serial_group = arg_parser.add_argument_group('Serial Reader')
@ -214,6 +217,7 @@ def argparse_add_reader_args(arg_parser):
return arg_parser
def init_reader(opts, **kwargs) -> Optional[LinkBase]:
"""
Init card reader driver
@ -231,15 +235,18 @@ def init_reader(opts, **kwargs) -> Optional[LinkBase]:
elif opts.modem_dev is not None:
print("Using modem for Generic SIM Access (3GPP TS 27.007)")
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
print("Using serial reader interface")
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
except Exception as e:
if str(e):
print("Card reader initialization failed with exception:\n" + str(e))
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

View File

@ -25,6 +25,7 @@ from pySim.transport import LinkBase
from pySim.exceptions import *
from pySim.utils import h2b, b2h
class L1CTLMessage(object):
# Every (encoded) L1CTL message has the following structure:
@ -42,6 +43,7 @@ class L1CTLMessage(object):
def gen_msg(self):
return struct.pack("!H", len(self.data)) + self.data
class L1CTLMessageReset(L1CTLMessage):
# L1CTL message types
@ -58,6 +60,7 @@ class L1CTLMessageReset(L1CTLMessage):
super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ)
self.data += struct.pack("Bxxx", type)
class L1CTLMessageSIM(L1CTLMessage):
# SIM related message types
@ -68,6 +71,7 @@ class L1CTLMessageSIM(L1CTLMessage):
super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ)
self.data += pdu
class CalypsoSimLink(LinkBase):
"""Transport Link for Calypso based phones."""
@ -75,7 +79,8 @@ class CalypsoSimLink(LinkBase):
super().__init__(**kwargs)
# Make sure that a given socket path exists
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)

View File

@ -27,8 +27,10 @@ from pySim.exceptions import *
# HACK: if somebody needs to debug this thing
# log.root.setLevel(log.DEBUG)
class ModemATCommandLink(LinkBase):
"""Transport Link for 3GPP TS 27.007 compliant modems."""
def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 115200, **kwargs):
super().__init__(**kwargs)
self._sl = serial.Serial(device, baudrate, timeout=5)
@ -82,7 +84,8 @@ class ModemATCommandLink(LinkBase):
break
time.sleep(patience)
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:
# Skip echo chars
@ -113,7 +116,8 @@ class ModemATCommandLink(LinkBase):
elif result[-1] == b'AT\r\r\nOK':
self._echo = True
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):
# Reset the modem, just to be sure

View File

@ -47,7 +47,8 @@ class PcscSimLink(LinkBase):
return
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:
cr.waitforcard()
except CardRequestTimeoutException:

View File

@ -168,7 +168,8 @@ class SerialSimLink(LinkBase):
self._sl.write(b)
r = self._sl.read()
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):
"""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)
r = self._sl.read(len(s))
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):
return self._sl.read()

View File

@ -110,6 +110,8 @@ FCP_Proprietary_TLV_MAP = {
}
# ETSI TS 102 221 11.1.1.4.3
def interpret_file_descriptor(in_hex):
in_bin = h2b(in_hex)
out = {}
@ -140,6 +142,8 @@ def interpret_file_descriptor(in_hex):
return out
# ETSI TS 102 221 11.1.1.4.9
def interpret_life_cycle_sts_int(in_hex):
lcsi = int(in_hex, 16)
if lcsi == 0x00:
@ -157,6 +161,7 @@ def interpret_life_cycle_sts_int(in_hex):
else:
return in_hex
# ETSI TS 102 221 11.1.1.4.10
FCP_Pin_Status_TLV_MAP = {
'90': 'ps_do',
@ -164,12 +169,14 @@ FCP_Pin_Status_TLV_MAP = {
'83': 'key_reference',
}
def interpret_ps_templ_do(in_hex):
# cannot use the 'TLV' parser due to repeating tags
#psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP)
# return psdo_tlv.parse(in_hex)
return in_hex
# 'interpreter' functions for each tag
FCP_interpreter_map = {
'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
# just the generic ValueError, so we cannot ignore those either. Instead,
# we insert a dict entry for every possible proprietary tag permitted
def fixup_fcp_proprietary_tlv_map(tlv_map):
if 'D0' in tlv_map:
return
@ -204,6 +213,7 @@ def tlv_key_replace(inmap, indata):
return key
return {newkey(inmap, d[0]): d[1] for d in indata.items()}
def tlv_val_interpret(inmap, indata):
def newval(inmap, key, val):
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
class _AM_DO_DF(DataObject):
def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80)
@ -262,6 +273,7 @@ class _AM_DO_DF(DataObject):
class _AM_DO_EF(DataObject):
"""ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31"""
def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80)
@ -306,8 +318,10 @@ class _AM_DO_EF(DataObject):
val |= 0x01
return val.to_bytes(1, 'big')
class _AM_DO_CHDR(DataObject):
"""Command Header Access Mode DO according to ISO 7816-4 Table 32."""
def __init__(self, tag):
super().__init__('command_header', 'Command Header Description', tag=tag)
@ -353,6 +367,7 @@ class _AM_DO_CHDR(DataObject):
res.append(self.decoded['P2'])
return res
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(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88),
@ -395,10 +410,13 @@ pin_names = bidict({
0x8e: 'ADM10',
})
class CRT_DO(DataObject):
"""Control Reference Template as per TS 102 221 9.5.1"""
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):
"""Decode a Control Reference Template DO."""
@ -407,7 +425,8 @@ class CRT_DO(DataObject):
if do[0] != 0x83 or do[1] != 0x01:
raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do)
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.decoded = pin_names[do[2]]
return do[6:]
@ -417,6 +436,8 @@ class CRT_DO(DataObject):
return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08'
# ISO7816-4 9.3.3 Table 33
class SecCondByte_DO(DataObject):
def __init__(self, tag=0x9d):
super().__init__('security_condition_byte', tag=tag)
@ -470,14 +491,18 @@ class SecCondByte_DO(DataObject):
raise ValueError('Unknown condition %s' % c)
return res.to_bytes(1, 'big')
Always_DO = TL0_DataObject('always', 'Always', 0x90)
Never_DO = TL0_DataObject('never', 'Never', 0x97)
class Nested_DO(DataObject):
"""A DO that nests another DO/Choice/Sequence"""
def __init__(self, name, tag, choice):
super().__init__(name, tag=tag)
self.children = choice
def from_bytes(self, binary: bytes) -> list:
remainder = binary
self.decoded = []
@ -485,10 +510,12 @@ class Nested_DO(DataObject):
rc, remainder = self.children.decode(remainder)
self.decoded.append(rc)
return self.decoded
def to_bytes(self) -> bytes:
encoded = [self.children.encode(d) for d in self.decoded]
return b''.join(encoded)
OR_Template = DataObjectChoice('or_template', 'OR-Template',
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
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])
# TS 102 221 Section 13.1
class EF_DIR(LinFixedEF):
class ApplicationLabel(BER_TLV_IE, tag=0x50):
# 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
# TS 102 221 Section 13.2
class EF_ICCID(TransparentEF):
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})
@ -533,14 +564,19 @@ class EF_ICCID(TransparentEF):
return enc_iccid(abstract['iccid'])
# TS 102 221 Section 13.3
class EF_PL(TransRecEF):
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):
if bin_data == b'\xff\xff':
return None
else:
return bin_data.decode('ascii')
def _encode_record_bin(self, in_json):
if in_json is None:
return b'\xff\xff'
@ -629,8 +665,11 @@ class EF_ARR(LinFixedEF):
class EF_UMPC(TransparentEF):
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})
addl_info = FlagsEnum(Byte, req_inc_idle_current=1, support_uicc_suspend=2)
self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
addl_info = FlagsEnum(Byte, req_inc_idle_current=1,
support_uicc_suspend=2)
self._construct = Struct(
'max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
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
def decode_select_response(resp_hex: str) -> object:
@ -741,6 +781,7 @@ class CardProfileUICC(CardProfile):
def match_with_card(scc: SimCardCommands) -> bool:
return match_uicc(scc)
class CardProfileUICCSIM(CardProfileUICC):
"""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
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 = {
1: 'Local Phone Book',
2: 'Fixed Dialling Numbers (FDN)',
@ -287,24 +302,9 @@ EF_USIM_ADF_map = {
# 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)
class EF_5GS3GPPNSC(LinFixedEF):
class NgKSI(BER_TLV_IE, tag=0x80):
_construct = Int8ub
@ -338,6 +338,8 @@ class EF_5GS3GPPNSC(LinFixedEF):
self._tlv = EF_5GS3GPPNSC.FiveGSNasSecurityContext
# 3GPP TS 31.102 Section 4.4.11.6
class EF_5GAUTHKEYS(TransparentEF):
class K_AUSF(BER_TLV_IE, tag=0x80):
_construct = HexAdapter(GreedyBytes)
@ -354,24 +356,32 @@ class EF_5GAUTHKEYS(TransparentEF):
self._tlv = EF_5GAUTHKEYS.FiveGAuthKeys
# 3GPP TS 31.102 Section 4.4.11.8
class ProtSchemeIdList(BER_TLV_IE, tag=0xa0):
# FIXME: 3GPP TS 24.501 Protection Scheme Identifier
# 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):
# 3GPP TS 24.501 / 3GPP TS 23.003
_construct = Int8ub
class HomeNetPubKey(BER_TLV_IE, tag=0x81):
# FIXME: RFC 5480
_construct = HexAdapter(GreedyBytes)
class HomeNetPubKeyList(BER_TLV_IE, tag=0xa1,
nested=[HomeNetPubKeyId, HomeNetPubKey]):
pass
# 3GPP TS 31.102 Section 4.4.11.6
class SUCI_CalcInfo(TLV_IE_Collection, nested=[ProtSchemeIdList, HomeNetPubKeyList]):
pass
@ -414,7 +424,8 @@ class EF_SUCI_Calc_Info(TransparentEF):
return out_bytes
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'])
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")
return {}
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
hnet_pubkey_id = in_bytes[pos:pos+hnet_pubkey_id_len][0]
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
pos += 1
# 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
# 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):
return h2b(self._encode_hex(in_json))
class EF_LI(TransRecEF):
def __init__(self, fid='6f05', sfid=None, name='EF.LI', size={2, None}, rec_len=2,
desc='Language Indication'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
def _decode_record_bin(self, in_bin):
if in_bin == b'\xff\xff':
return None
else:
# officially this is 7-bit GSM alphabet with one padding bit in each byte
return in_bin.decode('ascii')
def _encode_record_bin(self, in_json):
if in_json == None:
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
return in_json.encode('ascii')
class EF_Keys(TransparentEF):
def __init__(self, fid='6f08', sfid=0x08, name='EF.Keys', size={33, 33},
desc='Ciphering and Integrity Keys'):
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
class EF_HPPLMN(TransparentEF):
def __init__(self, fid='6f31', sfid=0x12, name='EF.HPPLMN', size={1, 1},
desc='Higher Priority PLMN search period'):
@ -527,18 +547,22 @@ class EF_HPPLMN(TransparentEF):
self._construct = Int8ub
# TS 31.102 Section 4.2.8
class EF_UServiceTable(TransparentEF):
def __init__(self, fid, sfid, name, desc, size, table):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, size=size)
self.table = table
# add those commands to the general commands of a TransparentEF
self.shell_commands += [self.AddlShellCommands()]
@staticmethod
def _bit_byte_offset_for_service(service: int) -> Tuple[int, int]:
i = service - 1
byte_offset = i//8
bit_offset = (i % 8)
return (byte_offset, bit_offset)
def _decode_bin(self, in_bin):
ret = {}
for i in range(0, len(in_bin)):
@ -551,25 +575,29 @@ class EF_UServiceTable(TransparentEF):
if service_nr in self.table:
ret[service_nr]['description'] = self.table[service_nr]
return ret
def _encode_bin(self, in_json):
# compute the required binary size
bin_len = 0
for srv in in_json.keys():
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:
bin_len = byte_offset+1
# encode the actual data
out = bytearray(b'\x00' * bin_len)
for srv in in_json.keys():
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:
bit = 1
else:
bit = 0
out[byte_offset] |= (bit) << bit_offset
return out
@with_default_category('File-Specific Commands')
class AddlShellCommands(CommandSet):
def __init__(self):
@ -584,14 +612,18 @@ class EF_UServiceTable(TransparentEF):
self._cmd.card.update_ust(int(arg), 0)
# TS 31.103 Section 4.2.7 - *not* the same as DF.GSM/EF.ECC!
class EF_ECC(LinFixedEF):
cc_construct = Rpad(BcdAdapter(Rpad(Bytes(3))), pattern='f')
category_construct = FlagsEnum(Byte, police=1, ambulance=2, fire_brigade=3, marine_guard=4,
mountain_rescue=5, manual_ecall=6, automatic_ecall=7)
alpha_construct = GsmStringAdapter(Rpad(GreedyBytes))
def __init__(self, fid='6fb7', sfid=0x01, name='EF.ECC',
desc='Emergency Call Codes'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={4, 20})
def _decode_record_bin(self, in_bin):
# mandatory parts
code = in_bin[:3]
@ -605,6 +637,7 @@ class EF_ECC(LinFixedEF):
alpha_id = in_bin[3:-1]
ret['alpha_id'] = parse_construct(EF_ECC.alpha_construct, alpha_id)
return ret
def _encode_record_bin(self, in_json):
if in_json is None:
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,
'lu_status'/Int8ub)
# TS 31.102 Section 4.2.18
class EF_AD(TransparentEF):
class OP_MODE(enum.IntEnum):
normal = 0x00
@ -645,6 +680,8 @@ class EF_AD(TransparentEF):
)
# TS 31.102 Section 4.2.23
class EF_PSLOCI(TransparentEF):
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)
@ -652,6 +689,8 @@ class EF_PSLOCI(TransparentEF):
'rai'/HexAdapter(Bytes(6)), 'rau_status'/Int8ub)
# TS 31.102 Section 4.2.33
class EF_ICI(CyclicEF):
def __init__(self, fid='6f80', sfid=0x14, name='EF.ICI', rec_len={28, 48},
desc='Incoming Call Information'):
@ -668,6 +707,8 @@ class EF_ICI(CyclicEF):
'link_to_phonebook'/Bytes(3))
# TS 31.102 Section 4.2.34
class EF_OCI(CyclicEF):
def __init__(self, fid='6f81', sfid=0x15, name='EF.OCI', rec_len={27, 47},
desc='Outgoing Call Information'):
@ -683,6 +724,8 @@ class EF_OCI(CyclicEF):
'link_to_phonebook'/Bytes(3))
# TS 31.102 Section 4.2.35
class EF_ICT(CyclicEF):
def __init__(self, fid='6f82', sfid=None, name='EF.ICT', rec_len={3, 3},
desc='Incoming Call Timer'):
@ -690,11 +733,16 @@ class EF_ICT(CyclicEF):
self._construct = Struct('accumulated_call_timer'/Int24ub)
# TS 31.102 Section 4.2.38
class EF_CCP2(LinFixedEF):
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
class EF_ACL(TransparentEF):
def __init__(self, fid='6f57', sfid=None, name='EF.ACL', size={32, None},
desc='Access Point Name Control List'):
@ -702,6 +750,8 @@ class EF_ACL(TransparentEF):
self._construct = Struct('num_of_apns'/Int8ub, 'tlvs'/GreedyBytes)
# TS 31.102 Section 4.2.51
class EF_START_HFN(TransparentEF):
def __init__(self, fid='6f5b', sfid=0x0f, name='EF.START-HFN', size={6, 6},
desc='Initialisation values for Hyperframe number'):
@ -709,6 +759,8 @@ class EF_START_HFN(TransparentEF):
self._construct = Struct('start_cs'/Int24ub, 'start_ps'/Int24ub)
# TS 31.102 Section 4.2.52
class EF_THRESHOLD(TransparentEF):
def __init__(self, fid='6f5c', sfid=0x10, name='EF.THRESHOLD', size={3, 3},
desc='Maximum value of START'):
@ -716,6 +768,8 @@ class EF_THRESHOLD(TransparentEF):
self._construct = Struct('max_start'/Int24ub)
# TS 31.102 Section 4.2.77
class EF_VGCSCA(TransRecEF):
def __init__(self, fid='6fd4', sfid=None, name='EF.VGCSCA', size={2, 100}, rec_len=2,
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)
# TS 31.102 Section 4.2.79
class EF_GBABP(TransparentEF):
def __init__(self, fid='6fd6', sfid=None, name='EF.GBABP', size={3, 50},
desc='GBA Bootstrapping parameters'):
@ -730,42 +786,61 @@ class EF_GBABP(TransparentEF):
self._construct = Struct('rand'/LV, 'b_tid'/LV, 'key_lifetime'/LV)
# TS 31.102 Section 4.2.80
class EF_MSK(LinFixedEF):
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)
self._construct = Struct('key_domain_id'/Bytes(3),
'num_msk_id'/Int8ub,
'msk_ids'/msk_ts_constr[this.num_msk_id])
# TS 31.102 Section 4.2.81
class EF_MUK(LinFixedEF):
class MUK_Idr(BER_TLV_IE, tag=0x80):
_construct = HexAdapter(GreedyBytes)
class MUK_Idi(BER_TLV_IE, tag=0x82):
_construct = HexAdapter(GreedyBytes)
class MUK_ID(BER_TLV_IE, tag=0xA0, nested=[MUK_Idr, MUK_Idi]):
pass
class TimeStampCounter(BER_TLV_IE, tag=0x81):
pass
class EF_MUK_Collection(TLV_IE_Collection, nested=[MUK_ID, TimeStampCounter]):
pass
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
# TS 31.102 Section 4.2.83
class EF_GBANL(LinFixedEF):
class NAF_ID(BER_TLV_IE, tag=0x80):
_construct = HexAdapter(GreedyBytes)
class B_TID(BER_TLV_IE, tag=0x81):
_construct = HexAdapter(GreedyBytes)
class EF_GBANL_Collection(BER_TLV_IE, nested=[NAF_ID, B_TID]):
pass
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
# TS 31.102 Section 4.2.85
class EF_EHPLMNPI(TransparentEF):
def __init__(self, fid='6fdb', sfid=None, name='EF.EHPLMNPI', size={1, 1},
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))
# TS 31.102 Section 4.2.87
class EF_NAFKCA(LinFixedEF):
class NAF_KeyCentreAddress(BER_TLV_IE, tag=0x80):
_construct = HexAdapter(GreedyBytes)
@ -783,23 +860,30 @@ class EF_NAFKCA(LinFixedEF):
self._tlv = EF_NAFKCA.NAF_KeyCentreAddress
# TS 31.102 Section 4.2.90
class EF_NCP_IP(LinFixedEF):
class DataDestAddrRange(TLV_IE, tag=0x83):
_construct = Struct('type_of_address'/Enum(Byte, IPv4=0x21, IPv6=0x56),
'prefix_length'/Int8ub,
'prefix'/HexAdapter(GreedyBytes))
class AccessPointName(TLV_IE, tag=0x80):
# coded as per TS 23.003
_construct = HexAdapter(GreedyBytes)
class Login(TLV_IE, tag=0x81):
# as per SMS DCS TS 23.038
_construct = GsmStringAdapter(GreedyBytes)
class Password(TLV_IE, tag=0x82):
# as per SMS DCS TS 23.038
_construct = GsmStringAdapter(GreedyBytes)
class BearerDescription(TLV_IE, tag=0x84):
# Bearer descriptionTLV DO as per TS 31.111
pass
class EF_NCP_IP_Collection(TLV_IE_Collection,
nested=[AccessPointName, Login, Password, BearerDescription]):
pass
@ -809,26 +893,36 @@ class EF_NCP_IP(LinFixedEF):
self._tlv = EF_NCP_IP.EF_NCP_IP_Collection
# TS 31.102 Section 4.2.91
class EF_EPSLOCI(TransparentEF):
def __init__(self, fid='6fe3', sfid=0x1e, name='EF.EPSLOCI', size={18, 18},
desc='EPS Location Information'):
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),
'eps_update_status'/upd_status_constr)
# TS 31.102 Section 4.2.92
class EF_EPSNSC(LinFixedEF):
class KSI_ASME(BER_TLV_IE, tag=0x80):
_construct = Int8ub
class K_ASME(BER_TLV_IE, tag=0x81):
_construct = HexAdapter(GreedyBytes)
class UplinkNASCount(BER_TLV_IE, tag=0x82):
_construct = Int32ub
class DownlinkNASCount(BER_TLV_IE, tag=0x83):
_construct = Int32ub
class IDofNASAlgorithms(BER_TLV_IE, tag=0x84):
_construct = HexAdapter(GreedyBytes)
class EPS_NAS_Security_Context(BER_TLV_IE, tag=0xa0,
nested=[KSI_ASME, K_ASME, UplinkNASCount, DownlinkNASCount,
IDofNASAlgorithms]):
@ -839,13 +933,18 @@ class EF_EPSNSC(LinFixedEF):
self._tlv = EF_EPSNSC.EPS_NAS_Security_Context
# TS 31.102 Section 4.2.96
class EF_PWS(TransparentEF):
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)
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)
# TS 31.102 Section 4.2.101
class EF_IPS(CyclicEF):
def __init__(self, fid='6ff1', sfid=None, name='EF.IPS', rec_len={4, 4},
desc='IMEI(SV) Pairing Status'):
@ -854,6 +953,8 @@ class EF_IPS(CyclicEF):
'link_to_ef_ipd'/Int8ub, 'rfu'/Byte)
# TS 31.102 Section 4.2.103
class EF_ePDGId(TransparentEF):
class ePDGId(BER_TLV_IE, tag=0x80, nested=[]):
_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"),
'IPv4': HexAdapter(GreedyBytes),
'IPv6': HexAdapter(GreedyBytes)}))
def __init__(self, fid='6ff3', sfid=None, name='EF.eDPDGId', desc='Home ePDG Identifier'):
super().__init__(fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_ePDGId.ePDGId
# TS 31.102 Section 4.2.106
class EF_FromPreferred(TransparentEF):
def __init__(self, fid='6ff7', sfid=None, name='EF.FromPreferred', size={1, 1},
desc='From Preferred'):
@ -877,15 +981,20 @@ class EF_FromPreferred(TransparentEF):
######################################################################
# TS 31.102 Section 4.4.11.2
class EF_5GS3GPPLOCI(TransparentEF):
def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size={20, 20},
desc='5S 3GP location information'):
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),
'5gs_update_status'/upd_status_constr)
# TS 31.102 Section 4.4.11.7
class EF_UAC_AIC(TransparentEF):
def __init__(self, fid='4f06', sfid=0x06, name='EF.UAC_AIC', size={4, 4},
desc='UAC Access Identities Configuration'):
@ -895,22 +1004,30 @@ class EF_UAC_AIC(TransparentEF):
self._construct = Struct('uac_access_id_config'/cfg_constr)
# TS 31.102 Section 4.4.11.9
class EF_OPL5G(LinFixedEF):
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)
# TS 31.102 Section 4.4.11.10
class EF_SUPI_NAI(TransparentEF):
class NetworkSpecificIdentifier(TLV_IE, tag=0x80):
# RFC 7542 encoded as UTF-8 string
_construct = GreedyString("utf8")
class GlobalLineIdentifier(TLV_IE, tag=0x81):
# TS 23.003 clause 28.16.2
pass
class GlobalCableIdentifier(TLV_IE, tag=0x82):
# TS 23.003 clause 28.15.2
pass
class NAI_TLV_Collection(TLV_IE_Collection,
nested=[NetworkSpecificIdentifier, GlobalLineIdentifier, GlobalCableIdentifier]):
pass
@ -919,6 +1036,7 @@ class EF_SUPI_NAI(TransparentEF):
super().__init__(fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_SUPI_NAI.NAI_TLV_Collection
class EF_TN3GPPSNN(TransparentEF):
class ServingNetworkName(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
@ -928,25 +1046,39 @@ class EF_TN3GPPSNN(TransparentEF):
self._tlv = EF_TN3GPPSNN.ServingNetworkName
# TS 31.102 Section 4.4.5
class DF_WLAN(CardDF):
def __init__(self, fid='5f40', name='DF.WLAN', desc='Files for WLAN purpose'):
super().__init__(fid=fid, name=name, desc=desc)
files = [
TransparentEF('4f41', 0x01, 'EF.Pseudo', 'Pseudonym'),
TransparentEF('4f42', 0x02, 'EF.UPLMNWLAN', 'User controlled PLMN selector for I-WLAN Access'),
TransparentEF('4f43', 0x03, 'EF.OPLMNWLAN', 'Operator controlled PLMN selector for I-WLAN Access'),
LinFixedEF('4f44', 0x04, 'EF.UWSIDL', 'User controlled WLAN Specific Identifier List'),
LinFixedEF('4f45', 0x05, 'EF.OWSIDL', 'Operator controlled WLAN Specific Identifier List'),
TransparentEF('4f46', 0x06, 'EF.WRI', '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'),
TransparentEF('4f42', 0x02, 'EF.UPLMNWLAN',
'User controlled PLMN selector for I-WLAN Access'),
TransparentEF('4f43', 0x03, 'EF.OPLMNWLAN',
'Operator controlled PLMN selector for I-WLAN Access'),
LinFixedEF('4f44', 0x04, 'EF.UWSIDL',
'User controlled WLAN Specific Identifier List'),
LinFixedEF('4f45', 0x05, 'EF.OWSIDL',
'Operator controlled WLAN Specific Identifier List'),
TransparentEF('4f46', 0x06, 'EF.WRI',
'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)
# TS 31.102 Section 4.4.6
class DF_HNB(CardDF):
def __init__(self, fid='5f50', name='DF.HNB', desc='Files for HomeNodeB purpose'):
super().__init__(fid=fid, name=name, desc=desc)
@ -961,47 +1093,65 @@ class DF_HNB(CardDF):
self.add_files(files)
# TS 31.102 Section 4.4.8
class DF_ProSe(CardDF):
def __init__(self, fid='5f90', name='DF.ProSe', desc='Files for ProSe purpose'):
super().__init__(fid=fid, name=name, desc=desc)
files = [
LinFixedEF('4f01', 0x01, 'EF.PROSE_MON', 'ProSe Monitoring Parameters'),
LinFixedEF('4f02', 0x02, 'EF.PROSE_ANN', 'ProSe Announcing Parameters'),
LinFixedEF('4f01', 0x01, 'EF.PROSE_MON',
'ProSe Monitoring Parameters'),
LinFixedEF('4f02', 0x02, 'EF.PROSE_ANN',
'ProSe Announcing Parameters'),
LinFixedEF('4f03', 0x03, 'EF.PROSEFUNC', 'HPLMN ProSe Function'),
TransparentEF('4f04', 0x04, 'EF.PROSE_RADIO_COM', 'ProSe Direct Communication Radio Parameters'),
TransparentEF('4f05', 0x05, 'EF.PROSE_RADIO_MON', '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'),
TransparentEF('4f04', 0x04, 'EF.PROSE_RADIO_COM',
'ProSe Direct Communication Radio Parameters'),
TransparentEF('4f05', 0x05, 'EF.PROSE_RADIO_MON',
'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'),
TransparentEF('4f09', 0x09, 'EF.PROSE_GC', 'ProSe Group Counter'),
TransparentEF('4f10', 0x10, 'EF.PST', 'ProSe Service Table'),
TransparentEF('4f11', 0x11, 'EF.UIRC', 'ProSe UsageInformationReportingConfiguration'),
LinFixedEF('4f12', 0x12, 'EF.PROSE_GM_DISCOVERY', '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'),
TransparentEF('4f11', 0x11, 'EF.UIRC',
'ProSe UsageInformationReportingConfiguration'),
LinFixedEF('4f12', 0x12, 'EF.PROSE_GM_DISCOVERY',
'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)
class DF_USIM_5GS(CardDF):
def __init__(self, fid='5FC0', name='DF.5GS', desc='5GS related files'):
super().__init__(fid=fid, name=name, desc=desc)
files = [
# I'm looking at 31.102 R16.6
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('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_UAC_AIC(),
EF_SUCI_Calc_Info(),
EF_OPL5G(),
EF_SUPI_NAI(),
TransparentEF('4F0A', 0x0a, 'EF.Routing_Indicator', 'Routing Indicator', size={4,4}),
TransparentEF('4F0B', 0x0b, 'EF.URSP', 'UE Route Selector Policies per PLMN'),
TransparentEF('4F0A', 0x0a, 'EF.Routing_Indicator',
'Routing Indicator', size={4, 4}),
TransparentEF('4F0B', 0x0b, 'EF.URSP',
'UE Route Selector Policies per PLMN'),
EF_TN3GPPSNN(),
]
self.add_files(files)
class ADF_USIM(CardADF):
def __init__(self, aid='a0000000871002', name='ADF.USIM', fid=None, sfid=None,
desc='USIM Application'):
@ -1013,20 +1163,25 @@ class ADF_USIM(CardADF):
EF_LI(sfid=0x02),
EF_IMSI(sfid=0x07),
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',
'User controlled PLMN Selector with Access Technology'),
EF_HPPLMN(),
EF_ACMmax(),
EF_UServiceTable('6f38', 0x04, 'EF.UST', 'USIM Service Table', size={1,17}, table=EF_UST_map),
CyclicEF('6f39', None, 'EF.ACM', 'Accumulated call meter', rec_len={3,3}),
EF_UServiceTable('6f38', 0x04, 'EF.UST', 'USIM Service Table', size={
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('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'),
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_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_AD(),
EF_CBMID(sfid=0x0e),
@ -1054,7 +1209,8 @@ class ADF_USIM(CardADF):
EF_ADN('6f4d', None, 'EF.BDN', 'Barred Dialling Numbers'),
EF_EXT('6f55', None, 'EF.EXT4', 'Extension4 (BDN/SSC)'),
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_DCK(),
EF_CNL(),
@ -1069,9 +1225,11 @@ class ADF_USIM(CardADF):
EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'),
EF_MBI(),
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)'),
TransparentEF('6fcd', None, 'EF.SPDI', 'Service Provider Display Information'),
TransparentEF('6fcd', None, 'EF.SPDI',
'Service Provider Display Information'),
EF_MMSN(),
EF_EXT('6fcf', None, 'EF.EXT8', 'Extension8 (MMSN)'),
EF_MMSICP(),
@ -1081,28 +1239,37 @@ class ADF_USIM(CardADF):
EF_VGCS(),
EF_VGCSS(),
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('6fd5', None, 'EF.VBCSCA', 'Voice Broadcast Service Ciphering Algorithm'),
EF_VGCSCA('6fd5', None, 'EF.VBCSCA',
'Voice Broadcast Service Ciphering Algorithm'),
EF_GBABP(),
EF_MSK(),
EF_MUK(),
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_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'),
EF_NCP_IP(),
EF_EPSLOCI('6fe3', 0x1e, 'EF.EPSLOCI', 'EPS location information'),
EF_EPSNSC(),
TransparentEF('6fe6', None, 'EF.UFC', 'USAT Facility Control', size={1,16}),
TransparentEF('6fe8', None, 'EF.NASCONFIG', 'Non Access Stratum Configuration'),
TransparentEF('6fe6', None, 'EF.UFC',
'USAT Facility Control', size={1, 16}),
TransparentEF('6fe8', None, 'EF.NASCONFIG',
'Non Access Stratum Configuration'),
# UICC IARI (only in cards that have no ISIM)
EF_PWS(),
LinFixedEF('6fed', None, 'EF.FDNURI', 'Fixed Dialling Numbers URI'),
LinFixedEF('6fee', None, 'EF.BDNURI', 'Barred Dialling Numbers URI'),
LinFixedEF('6fef', None, 'EF.SDNURI', 'Service Dialling Numbers URI'),
LinFixedEF('6fed', None, 'EF.FDNURI',
'Fixed Dialling Numbers URI'),
LinFixedEF('6fee', None, 'EF.BDNURI',
'Barred Dialling Numbers URI'),
LinFixedEF('6fef', None, 'EF.SDNURI',
'Service Dialling Numbers URI'),
EF_IPS(),
EF_ePDGId(),
# 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('autn', help='Authentication Nonce')
#authenticate_parser.add_argument('--context', help='Authentication context', default='3G')
@cmd2.with_argparser(authenticate_parser)
def do_authenticate(self, opts):
"""Perform Authentication and Key Agreement (AKA)."""
@ -1151,7 +1319,8 @@ class ADF_USIM(CardADF):
"""Send an ENVELOPE command to the card."""
tpdu_ie = SMS_TPDU()
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])
(data, sw) = self._cmd.card._scc.envelope(b2h(sms_dl.to_tlv()))
self._cmd.poutput('SW: %s, data: %s' % (sw, data))
@ -1168,6 +1337,7 @@ sw_usim = {
}
}
class CardApplicationUSIM(CardApplication):
def __init__(self):
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
class EF_IMPI(TransparentEF):
class nai(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
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)
self._tlv = EF_IMPI.nai
# TS 31.103 Section 4.2.3
class EF_DOMAIN(TransparentEF):
class domain(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
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)
self._tlv = EF_DOMAIN.domain
# TS 31.103 Section 4.2.4
class EF_IMPU(LinFixedEF):
class impu(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
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)
self._tlv = EF_IMPU.impu
# TS 31.103 Section 4.2.8
class EF_PCSCF(LinFixedEF):
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)
def _decode_record_hex(self, raw_hex):
addr, addr_type = dec_addr_tlv(raw_hex)
return {"addr": addr, "addr_type": addr_type}
def _encode_record_hex(self, json_in):
addr = json_in['addr']
addr_type = json_in['addr_type']
return enc_addr_tlv(addr, addr_type)
# TS 31.103 Section 4.2.9
class EF_GBABP(TransparentEF):
def __init__(self, fid='6fd5', sfid=None, name='EF.GBABP', desc='GBA Bootstrapping'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.10
class EF_GBANL(LinFixedEF):
def __init__(self, fid='6fd7', sfid=None, name='EF.GBANL', desc='GBA NAF List'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.11
class EF_NAFKCA(LinFixedEF):
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)
# TS 31.103 Section 4.2.16
class EF_UICCIARI(LinFixedEF):
class iari(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
def __init__(self, fid='6fe7', sfid=None, name='EF.UICCIARI', desc='UICC IARI'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_UICCIARI.iari
# TS 31.103 Section 4.2.18
class EF_IMSConfigData(BerTlvEF):
def __init__(self, fid='6ff8', sfid=None, name='EF.IMSConfigData', desc='IMS Configuration Data'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.19
class EF_XCAPConfigData(BerTlvEF):
def __init__(self, fid='6ffc', sfid=None, name='EF.XCAPConfigData', desc='XCAP Configuration Data'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
# TS 31.103 Section 4.2.20
class EF_WebRTCURI(TransparentEF):
class uri(BER_TLV_IE, tag=0x80):
_construct = GreedyString("utf8")
def __init__(self, fid='6ffa', sfid=None, name='EF.WebRTCURI', desc='WebRTC URI'):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_WebRTCURI.uri
# TS 31.103 Section 4.2.21
class EF_MuDMiDConfigData(BerTlvEF):
def __init__(self, fid='6ffe', sfid=None, name='EF.MuDMiDConfigData',
desc='MuD and MiD Configuration Data'):
@ -172,7 +203,8 @@ class ADF_ISIM(CardADF):
EF_IMPU(),
EF_AD(),
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_GBABP(),
EF_GBANL(),
@ -195,6 +227,7 @@ class ADF_ISIM(CardADF):
def decode_select_response(self, data_hex):
return pySim.ts_102_221.CardProfileUICC.decode_select_response(data_hex)
# TS 31.103 Section 7.1
sw_isim = {
'Security management': {
@ -203,6 +236,7 @@ sw_isim = {
}
}
class CardApplicationISIM(CardApplication):
def __init__(self):
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/>.
#
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'
DF_num = {
@ -323,27 +334,18 @@ EF_SST_map = {
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
######################################################################
# TS 51.011 Section 10.5.1
class EF_ADN(LinFixedEF):
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):
alpha_id_len = len(raw_bin_data) - 14
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]}
# TS 51.011 Section 10.5.5
class EF_SMS(LinFixedEF):
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_status(status):
if status & 0x01 == 0x00:
@ -384,51 +390,72 @@ class EF_SMS(LinFixedEF):
# TS 51.011 Section 10.5.5
class EF_MSISDN(LinFixedEF):
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):
return {'msisdn': dec_msisdn(raw_hex_data)}
def _encode_record_hex(self, abstract):
msisdn = abstract['msisdn']
if type(msisdn) == str:
encoded_msisdn = enc_msisdn(msisdn)
else:
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
# TS 51.011 Section 10.5.6
class EF_SMSP(LinFixedEF):
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
class EF_SMSS(TransparentEF):
class MemCapAdapter(Adapter):
def _decode(self, obj, context, path):
return False if obj & 1 else True
def _encode(self, obj, context, path):
return 0 if obj else 1
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)
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
class EF_SMSR(LinFixedEF):
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)
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):
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)
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
class EF_CMI(LinFixedEF):
def __init__(self, fid='6f58', sfid=None, name='EF.CMI', rec_len={2, 21},
desc='Comparison Method Information'):
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):
def __init__(self, fid='7f10', name='DF.TELECOM', desc=None):
@ -437,8 +464,10 @@ class DF_TELECOM(CardDF):
EF_ADN(),
EF_ADN(fid='6f3b', name='EF.FDN', desc='Fixed dialling numbers'),
EF_SMS(),
LinFixedEF(fid='6f3d', name='EF.CCP', desc='Capability Configuration Parameters', rec_len={14,14}),
LinFixedEF(fid='6f4f', name='EF.ECCP', desc='Extended Capability Configuration Parameters', rec_len={15,32}),
LinFixedEF(fid='6f3d', name='EF.CCP',
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_SMSP(),
EF_SMSS(),
@ -458,25 +487,34 @@ class DF_TELECOM(CardDF):
######################################################################
# TS 51.011 Section 10.3.1
class EF_LP(TransRecEF):
def __init__(self, fid='6f05', sfid=None, name='EF.LP', size={1, None}, rec_len=1,
desc='Language Preference'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
def _decode_record_bin(self, in_bin):
return b2h(in_bin)
def _encode_record_bin(self, in_json):
return h2b(in_json)
# TS 51.011 Section 10.3.2
class EF_IMSI(TransparentEF):
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)
# add those commands to the general commands of a TransparentEF
self.shell_commands += [self.AddlShellCommands(self)]
def _decode_hex(self, raw_hex):
return {'imsi': dec_imsi(raw_hex)}
def _encode_hex(self, abstract):
return enc_imsi(abstract['imsi'])
@with_default_category('File-Specific Commands')
class AddlShellCommands(CommandSet):
def __init__(self, ef: TransparentEF):
@ -491,9 +529,11 @@ class EF_IMSI(TransparentEF):
if sw == '9000' and len(data['imsi'])-len(plmn) == 10:
imsi = data['imsi']
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:
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:
raise ValueError("PLMN length does not match IMSI length")
else:
@ -505,11 +545,13 @@ class EF_PLMNsel(TransRecEF):
def __init__(self, fid='6f30', sfid=None, name='EF.PLMNsel', desc='PLMN selector',
size={24, None}, rec_len=3):
super().__init__(fid, name=name, sfid=sfid, desc=desc, size=size, rec_len=rec_len)
def _decode_record_hex(self, in_hex):
if in_hex[:6] == "ffffff":
return None
else:
return dec_plmn(in_hex)
def _encode_record_hex(self, in_json):
if in_json == None:
return "ffffff"
@ -517,6 +559,8 @@ class EF_PLMNsel(TransRecEF):
return enc_plmn(in_json['mcc'], in_json['mnc'])
# TS 51.011 Section 10.3.6
class EF_ACMmax(TransparentEF):
def __init__(self, fid='6f37', sfid=None, name='EF.ACMmax', size={3, 3},
desc='ACM maximum value'):
@ -524,16 +568,20 @@ class EF_ACMmax(TransparentEF):
self._construct = Struct('acm_max'/Int24ub)
# TS 51.011 Section 10.3.7
class EF_ServiceTable(TransparentEF):
def __init__(self, fid, sfid, name, desc, size, table):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size)
self.table = table
@staticmethod
def _bit_byte_offset_for_service(service: int) -> Tuple[int, int]:
i = service - 1
byte_offset = i//4
bit_offset = (i % 4) * 2
return (byte_offset, bit_offset)
def _decode_bin(self, raw_bin):
ret = {}
for i in range(0, len(raw_bin)*4):
@ -547,19 +595,22 @@ class EF_ServiceTable(TransparentEF):
'activated': True if bits & 2 else False,
}
return ret
def _encode_bin(self, in_json):
# compute the required binary size
bin_len = 0
for srv in in_json.keys():
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:
bin_len = byte_offset+1
# encode the actual data
out = bytearray(b'\x00' * bin_len)
for srv in in_json.keys():
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
if in_json[srv]['allocated'] == True:
bits |= 1
@ -569,6 +620,8 @@ class EF_ServiceTable(TransparentEF):
return out
# TS 51.011 Section 10.3.11
class EF_SPN(TransparentEF):
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)
@ -582,6 +635,8 @@ class EF_SPN(TransparentEF):
)
# TS 51.011 Section 10.3.13
class EF_CBMI(TransRecEF):
def __init__(self, fid='6f45', sfid=None, name='EF.CBMI', size={2, None}, rec_len=2,
desc='Cell Broadcast message identifier selection'):
@ -589,15 +644,21 @@ class EF_CBMI(TransRecEF):
self._construct = GreedyRange(Int16ub)
# TS 51.011 Section 10.3.15
class EF_ACC(TransparentEF):
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)
def _decode_bin(self, raw_bin):
return {'acc': unpack('!H', raw_bin)[0]}
def _encode_bin(self, abstract):
return pack('!H', abstract['acc'])
# TS 51.011 Section 10.3.16
class EF_LOCI(TransparentEF):
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)
@ -606,6 +667,8 @@ class EF_LOCI(TransparentEF):
location_area_not_allowed=3))
# TS 51.011 Section 10.3.18
class EF_AD(TransparentEF):
class OP_MODE(enum.IntEnum):
normal = 0x00
@ -637,6 +700,8 @@ class EF_AD(TransparentEF):
)
# TS 51.011 Section 10.3.20 / 10.3.22
class EF_VGCS(TransRecEF):
def __init__(self, fid='6fb1', sfid=None, name='EF.VGCS', size={4, 200}, rec_len=4,
desc='Voice Group Call Service'):
@ -644,29 +709,41 @@ class EF_VGCS(TransRecEF):
self._construct = BcdAdapter(Bytes(4))
# TS 51.011 Section 10.3.21 / 10.3.23
class EF_VGCSS(TransparentEF):
def __init__(self, fid='6fb2', sfid=None, name='EF.VGCSS', size={7, 7},
desc='Voice Group Call Service Status'):
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
class EF_eMLPP(TransparentEF):
def __init__(self, fid='6fb5', sfid=None, name='EF.eMLPP', size={2, 2},
desc='enhanced Multi Level Pre-emption and Priority'):
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)
self._construct = Struct('levels'/FlagsConstruct, 'fast_call_setup_cond'/FlagsConstruct)
FlagsConstruct = FlagsEnum(
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
class EF_AAeM(TransparentEF):
def __init__(self, fid='6fb6', sfid=None, name='EF.AAeM', size={1, 1},
desc='Automatic Answer for eMLPP Service'):
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)
# TS 51.011 Section 10.3.26
class EF_CBMID(EF_CBMI):
def __init__(self, fid='6f48', sfid=None, name='EF.CBMID', size={2, None}, rec_len=2,
desc='Cell Broadcast Message Identifier for Data Download'):
@ -674,6 +751,8 @@ class EF_CBMID(EF_CBMI):
self._construct = GreedyRange(Int16ub)
# TS 51.011 Section 10.3.27
class EF_ECC(TransRecEF):
def __init__(self, fid='6fb7', sfid=None, name='EF.ECC', size={3, 15}, rec_len=3,
desc='Emergency Call Codes'):
@ -681,6 +760,8 @@ class EF_ECC(TransRecEF):
self._construct = GreedyRange(BcdAdapter(Bytes(3)))
# TS 51.011 Section 10.3.28
class EF_CBMIR(TransRecEF):
def __init__(self, fid='6f50', sfid=None, name='EF.CBMIR', size={4, None}, rec_len=4,
desc='Cell Broadcast message identifier range selection'):
@ -688,6 +769,8 @@ class EF_CBMIR(TransRecEF):
self._construct = GreedyRange(Struct('lower'/Int16ub, 'upper'/Int16ub))
# TS 51.011 Section 10.3.29
class EF_DCK(TransparentEF):
def __init__(self, fid='6f2c', sfid=None, name='EF.DCK', size={16, 16},
desc='Depersonalisation Control Keys'):
@ -697,10 +780,13 @@ class EF_DCK(TransparentEF):
'service_provider'/BcdAdapter(Bytes(4)),
'corporate'/BcdAdapter(Bytes(4)))
# TS 51.011 Section 10.3.30
class EF_CNL(TransRecEF):
def __init__(self, fid='6f32', sfid=None, name='EF.CNL', size={6, None}, rec_len=6,
desc='Co-operative Network List'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len)
def _decode_record_hex(self, in_hex):
(in_plmn, sub, svp, corp) = unpack('!3sBBB', h2b(in_hex))
res = dec_plmn(b2h(in_plmn))
@ -708,6 +794,7 @@ class EF_CNL(TransRecEF):
res['service_provider_id'] = svp
res['corporate_id'] = corp
return res
def _encode_record_hex(self, in_json):
plmn = enc_plmn(in_json['mcc'], in_json['mnc'])
return b2h(pack('!3sBBB',
@ -717,19 +804,26 @@ class EF_CNL(TransRecEF):
in_json['corporate_id']))
# TS 51.011 Section 10.3.31
class EF_NIA(LinFixedEF):
def __init__(self, fid='6f51', sfid=None, name='EF.NIA', rec_len={1, 32},
desc='Network\'s Indication of Alerting'):
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
class EF_Kc(TransparentEF):
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)
self._construct = Struct('kc'/HexAdapter(Bytes(8)), 'cksn'/Int8ub)
# TS 51.011 Section 10.3.33
class EF_LOCIGPRS(TransparentEF):
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)
@ -738,14 +832,18 @@ class EF_LOCIGPRS(TransparentEF):
routing_area_not_allowed=3))
# TS 51.011 Section 10.3.35..37
class EF_xPLMNwAcT(TransRecEF):
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)
def _decode_record_hex(self, in_hex):
if in_hex[:6] == "ffffff":
return None
else:
return dec_xplmn_w_act(in_hex)
def _encode_record_hex(self, in_json):
if in_json == None:
return "ffffff0000"
@ -753,6 +851,7 @@ class EF_xPLMNwAcT(TransRecEF):
hplmn = enc_plmn(in_json['mcc'], in_json['mnc'])
act = self.enc_act(in_json['act'])
return hplmn + act
@staticmethod
def enc_act(in_list):
u16 = 0
@ -784,6 +883,8 @@ class EF_xPLMNwAcT(TransRecEF):
return '%04X' % (u16)
# TS 51.011 Section 10.3.38
class EF_CPBCCH(TransRecEF):
def __init__(self, fid='6f63', sfid=None, name='EF.CPBCCH', size={2, 14}, rec_len=2,
desc='CPBCCH Information'):
@ -791,33 +892,45 @@ class EF_CPBCCH(TransRecEF):
self._construct = Struct('cpbcch'/Int16ub)
# TS 51.011 Section 10.3.39
class EF_InvScan(TransparentEF):
def __init__(self, fid='6f64', sfid=None, name='EF.InvScan', size={1, 1},
desc='IOnvestigation Scan'):
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
class EF_PNN(LinFixedEF):
class FullNameForNetwork(BER_TLV_IE, tag=0x43):
# TS 24.008 10.5.3.5a
pass
class ShortNameForNetwork(BER_TLV_IE, tag=0x45):
# TS 24.008 10.5.3.5a
pass
class NetworkNameCollection(TLV_IE_Collection, nested=[FullNameForNetwork, ShortNameForNetwork]):
pass
def __init__(self, fid='6fc5', sfid=None, name='EF.PNN', desc='PLMN Network Name'):
super().__init__(fid, sfid=sfid, name=name, desc=desc)
self._tlv = EF_PNN.NetworkNameCollection
# TS 51.011 Section 10.3.42
class EF_OPL(LinFixedEF):
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)
self._construct = Struct('lai'/Bytes(5), 'pnn_record_id'/Int8ub)
# TS 51.011 Section 10.3.44 + TS 31.102 4.2.62
class EF_MBI(LinFixedEF):
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)
@ -825,6 +938,8 @@ class EF_MBI(LinFixedEF):
'mbi_other'/Int8ub, 'mbi_videocall'/COptional(Int8ub))
# TS 51.011 Section 10.3.45 + TS 31.102 4.2.63
class EF_MWIS(LinFixedEF):
def __init__(self, fid='6fca', sfid=None, name='EF.MWIS', rec_len={5, 6},
desc='Message Waiting Indication Status'):
@ -835,10 +950,13 @@ class EF_MWIS(LinFixedEF):
'num_waiting_other'/Int8ub, 'num_waiting_videomail'/COptional(Int8ub))
# TS 51.011 Section 10.3.66
class EF_SPDI(TransparentEF):
class ServiceProviderPLMN(BER_TLV_IE, tag=0x80):
# flexible numbers of 3-byte PLMN records
_construct = GreedyRange(BcdAdapter(Bytes(3)))
class SPDI(BER_TLV_IE, tag=0xA3, nested=[ServiceProviderPLMN]):
pass
def __init__(self, fid='6fcd', sfid=None, name='EF.SPDI',
@ -847,6 +965,8 @@ class EF_SPDI(TransparentEF):
self._tlv = EF_SPDI.SPDI
# TS 51.011 Section 10.3.51
class EF_MMSN(LinFixedEF):
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)
@ -854,20 +974,27 @@ class EF_MMSN(LinFixedEF):
'mms_notification'/Bytes(this._.total_len-4), 'ext_record_nr'/Byte)
# TS 51.011 Annex K.1
class MMS_Implementation(BER_TLV_IE, tag=0x80):
_construct = FlagsEnum(Byte, WAP=1)
# TS 51.011 Section 10.3.53
class EF_MMSICP(TransparentEF):
class MMS_Relay_Server(BER_TLV_IE, tag=0x81):
# 3GPP TS 23.140
pass
class Interface_to_CN(BER_TLV_IE, tag=0x82):
# 3GPP TS 23.140
pass
class Gateway(BER_TLV_IE, tag=0x83):
# Address, Type of address, Port, Service, AuthType, AuthId, AuthPass / 3GPP TS 23.140
pass
class MMS_ConnectivityParamters(TLV_IE_Collection,
nested=[MMS_Implementation, MMS_Relay_Server, Interface_to_CN, Gateway]):
pass
@ -877,11 +1004,15 @@ class EF_MMSICP(TransparentEF):
self._tlv = EF_MMSICP.MMS_ConnectivityParamters
# TS 51.011 Section 10.3.54
class EF_MMSUP(LinFixedEF):
class MMS_UserPref_ProfileName(BER_TLV_IE, tag=0x81):
pass
class MMS_UserPref_Info(BER_TLV_IE, tag=0x82):
pass
class MMS_User_Preferences(TLV_IE_Collection,
nested=[MMS_Implementation, MMS_UserPref_ProfileName, MMS_UserPref_Info]):
pass
@ -891,6 +1022,8 @@ class EF_MMSUP(LinFixedEF):
self._tlv = EF_MMSUP.MMS_User_Preferences
# TS 51.011 Section 10.3.55
class EF_MMSUCP(TransparentEF):
def __init__(self, fid='6fd2', sfid=None, name='EF.MMSUCP', size={1, None},
desc='MMS User Connectivity Parameters'):
@ -905,25 +1038,33 @@ class DF_GSM(CardDF):
EF_IMSI(),
EF_Kc(),
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_ServiceTable('6f38', None, 'EF.SST', 'SIM service table', table=EF_SST_map, size={2,16}),
CyclicEF('6f39', None, 'EF.ACM', 'Accumulated call meter', rec_len={3,3}),
EF_ServiceTable('6f38', None, 'EF.SST',
'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('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'),
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(),
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_PLMNsel('6f7b', None, 'EF.FPLMN', 'Forbidden PLMNs', size={12,12}),
EF_PLMNsel('6f7b', None, 'EF.FPLMN',
'Forbidden PLMNs', size={12, 12}),
EF_LOCI(),
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_VGCSS(),
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_AAeM(),
EF_CBMID(),
@ -939,7 +1080,8 @@ class DF_GSM(CardDF):
'User controlled PLMN Selector with Access Technology'),
EF_xPLMNwAcT('6f61', None, 'EF.OPLMNwAcT',
'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_InvScan(),
EF_PNN(),
@ -947,7 +1089,8 @@ class DF_GSM(CardDF):
EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'),
EF_MBI(),
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('6fcc', None, 'EF.EXT7', 'Externsion7 (CFIS)'),
EF_SPDI(),
@ -959,6 +1102,7 @@ class DF_GSM(CardDF):
]
self.add_files(files)
class CardProfileSIM(CardProfile):
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
def decode_select_response(resp_hex: str) -> object:
@ -1021,8 +1166,10 @@ class CardProfileSIM(CardProfile):
'proprietary_info': {},
}
ret['file_id'] = b2h(resp_bin[4:6])
ret['proprietary_info']['available_memory'] = int.from_bytes(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['proprietary_info']['available_memory'] = int.from_bytes(
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
if file_type in ['mf', 'df']:
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])
# CHV / UNBLOCK CHV stats
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['access_conditions'] = b2h(resp_bin[8:10])
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
Hexstr = str
def h2b(s: Hexstr) -> bytearray:
"""convert from a string of hex nibbles to a sequence of bytes"""
return bytearray.fromhex(s)
def b2h(b: bytearray) -> Hexstr:
"""convert from a sequence of bytes to a string of hex nibbles"""
return ''.join(['%02x' % (x) for x in b])
def h2i(s: Hexstr) -> List[int]:
"""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])]
def i2h(s: List[int]) -> Hexstr:
"""convert from a list of integers to a string of hex nibbles"""
return ''.join(['%02x' % (x) for x in s])
def h2s(s: Hexstr) -> str:
"""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])
if int(x + y, 16) != 0xff])
def s2h(s: str) -> Hexstr:
"""convert from an ASCII string to a string of hex nibbles"""
b = bytearray()
b.extend(map(ord, s))
return b2h(b)
def i2s(s: List[int]) -> str:
"""convert from a list of integers to an ASCII string"""
return ''.join([chr(x) for x in s])
def swap_nibbles(s: Hexstr) -> Hexstr:
"""swap the nibbles in a hex string"""
return ''.join([x+y for x, y in zip(s[1::2], s[0::2])])
def rpad(s: str, l: int, c='f') -> str:
"""pad string on the right side.
Args:
@ -75,6 +84,7 @@ def rpad(s:str, l:int, c='f') -> str:
"""
return s + c * (l - len(s))
def lpad(s: str, l: int, c='f') -> str:
"""pad string on the left side.
Args:
@ -86,9 +96,11 @@ def lpad(s:str, l:int, c='f') -> str:
"""
return c * (l - len(s)) + s
def half_round_up(n: int) -> int:
return (n + 1)//2
def str_sanitize(s: str) -> str:
"""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.
@ -107,10 +119,12 @@ def str_sanitize(s:str) -> str:
# poor man's COMPREHENSION-TLV decoder.
#########################################################################
def comprehensiontlv_parse_tag_raw(binary: bytes) -> Tuple[int, bytes]:
"""Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
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:
# three-byte tag
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]
return (tag, binary[1:])
def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
"""Parse a single Tag according to ETSI TS 101 220 Section 7.1.1"""
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:
# three-byte tag
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
return ({'comprehension': compr, 'tag': tag}, binary[1:])
def comprehensiontlv_encode_tag(tag) -> bytes:
"""Encode a single Tag according to ETSI TS 101 220 Section 7.1.1"""
# 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
def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
"""Parse a single TLV IE at the start of the given binary data.
Args:
@ -175,7 +193,6 @@ def comprehensiontlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
return (tagdict, length, value, remainder)
#########################################################################
# 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
return tag, binary[i:]
def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]:
"""Parse a single Tag value according to ITU-T X.690 8.1.2
Args:
@ -227,6 +245,7 @@ def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]:
i += 1
return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:])
def bertlv_encode_tag(t) -> bytes:
"""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
return tag_bytes
def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]:
"""Parse a single Length value according to ITU-T X.690 8.1.3;
only the definite form is supported here.
@ -290,6 +310,7 @@ def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]:
length |= binary[i]
return (length, binary[1+num_len_oct:])
def bertlv_encode_len(length: int) -> bytes:
"""Encode a single Length value according to ITU-T X.690 8.1.3;
only the definite form is supported here.
@ -311,6 +332,7 @@ def bertlv_encode_len(length:int) -> bytes:
else:
raise ValueError("Length > 32bits not supported")
def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]:
"""Parse a single TLV IE at the start of the given binary data.
Args:
@ -325,7 +347,6 @@ def bertlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
return (tagdict, length, value, remainder)
# IMSI encoded format:
# For IMSI 0123456789ABCDE:
#
@ -343,11 +364,13 @@ def bertlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]:
def enc_imsi(imsi: str):
"""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)
ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15)))
return ei
def dec_imsi(ef: Hexstr) -> Optional[str]:
"""Converts an EF value to the IMSI string representation"""
if len(ef) < 4:
@ -366,12 +389,15 @@ def dec_imsi(ef:Hexstr) -> Optional[str]:
imsi = swapped[1:]
return imsi
def dec_iccid(ef: Hexstr) -> str:
return swap_nibbles(ef).strip('f')
def enc_iccid(iccid: str) -> Hexstr:
return swap_nibbles(rpad(iccid, 20))
def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr:
"""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])
def dec_plmn(threehexbytes: Hexstr) -> dict:
res = {'mcc': "0", 'mnc': "0"}
dec_mcc_from_plmn_str(threehexbytes)
@ -405,6 +432,7 @@ def dec_plmn(threehexbytes:Hexstr) -> dict:
res['mnc'] = dec_mnc_from_plmn_str(threehexbytes)
return res
def dec_spn(ef):
"""Obsolete, kept for API compatibility"""
from ts_51_011 import EF_SPN
@ -414,6 +442,7 @@ def dec_spn(ef):
name = abstract_data['spn']
return (name, show_in_hplmn, hide_in_oplmn)
def enc_spn(name: str, show_in_hplmn=False, hide_in_oplmn=False):
"""Obsolete, kept for API compatibility"""
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)
def hexstr_to_Nbytearr(s, nbytes):
return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))]
# Accepts hex string representing three bytes
def dec_mcc_from_plmn(plmn: Hexstr) -> int:
ia = h2i(plmn)
digit1 = ia[0] & 0x0F # 1st byte, LSB
@ -437,6 +469,7 @@ def dec_mcc_from_plmn(plmn:Hexstr) -> int:
return 0xFFF # 4095
return derive_mcc(digit1, digit2, digit3)
def dec_mcc_from_plmn_str(plmn: Hexstr) -> str:
digit1 = plmn[1] # 1st byte, LSB
digit2 = plmn[0] # 1st byte, MSB
@ -444,6 +477,7 @@ def dec_mcc_from_plmn_str(plmn:Hexstr) -> str:
res = digit1 + digit2 + digit3
return res.upper().strip("F")
def dec_mnc_from_plmn(plmn: Hexstr) -> int:
ia = h2i(plmn)
digit1 = ia[2] & 0x0F # 3rd byte, LSB
@ -453,6 +487,7 @@ def dec_mnc_from_plmn(plmn:Hexstr) -> int:
return 0xFFF # 4095
return derive_mnc(digit1, digit2, digit3)
def dec_mnc_from_plmn_str(plmn: Hexstr) -> str:
digit1 = plmn[5] # 3rd byte, LSB
digit2 = plmn[4] # 3rd byte, MSB
@ -460,6 +495,7 @@ def dec_mnc_from_plmn_str(plmn:Hexstr) -> str:
res = digit1 + digit2 + digit3
return res.upper().strip("F")
def dec_act(twohexbytes: Hexstr) -> List[str]:
act_list = [
{'bit': 15, 'name': "UTRAN"},
@ -491,17 +527,21 @@ def dec_act(twohexbytes:Hexstr) -> List[str]:
sel.append(a['name'])
return sel
def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]:
res = {'mcc': "0", 'mnc': "0", 'act': []}
plmn_chars = 6
act_chars = 4
plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
# first three bytes (six ascii hex chars)
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['mnc'] = dec_mnc_from_plmn_str(plmn_str)
res['act'] = dec_act(act_str)
return res
def format_xplmn_w_act(hexstr):
s = ""
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'] == "":
rec_str = "unused"
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)
return s
def dec_loci(hexstr):
res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0}
res['tmsi'] = hexstr[:8]
@ -522,8 +564,10 @@ def dec_loci(hexstr):
res['status'] = h2i(hexstr[20:22])
return res
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-sig'] = hexstr[8:14]
res['mcc'] = dec_mcc_from_plmn(hexstr[14:20])
@ -533,6 +577,7 @@ def dec_psloci(hexstr):
res['status'] = h2i(hexstr[26:28])
return res
def dec_epsloci(hexstr):
res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0}
res['guti'] = hexstr[:24]
@ -543,14 +588,17 @@ def dec_epsloci(hexstr):
res['status'] = h2i(hexstr[34:36])
return res
def dec_xplmn(threehexbytes: Hexstr) -> dict:
res = {'mcc': 0, 'mnc': 0, 'act': []}
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['mnc'] = dec_mnc_from_plmn(plmn_str)
return res
def format_xplmn(hexstr: Hexstr) -> str:
s = ""
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:
rec_str = "unused"
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)
return s
def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr:
"""
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)
return b2h(strxor(opc_bytes, op_bytes))
def calculate_luhn(cc) -> int:
"""
Calculate Luhn checksum used in e.g. ICCID and IMEI
"""
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
def mcc_from_imsi(imsi: str) -> Optional[str]:
"""
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:
return None
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
@ -613,6 +667,7 @@ def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]:
else:
return None
def derive_mcc(digit1: int, digit2: int, digit3: int) -> int:
"""
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
def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int:
"""
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
def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]:
"""
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:
return None
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
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)
def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr:
"""
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)
# bit = 1: service 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
return avail_st
def first_TLV_parser(bytelist):
'''
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]
return (Tag, Len, Val)
def TLV_parser(bytelist):
'''
TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...]
@ -800,6 +862,7 @@ def TLV_parser(bytelist):
bytelist = bytelist[L+2:]
return ret
def enc_st(st, service, state=1):
"""
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
def dec_addr_tlv(hexstr):
"""
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)
def enc_addr_tlv(addr, addr_type='00'):
"""
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
def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool:
"""
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:
return False
def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr:
"""
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_encode = h2b(pin_adm)
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:
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
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.
@ -983,6 +1053,7 @@ def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='
content = rpad(content, len(hexstr))
return content
def dec_ePDGSelection(sixhexbytes):
"""
Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm.
@ -996,15 +1067,18 @@ def dec_ePDGSelection(sixhexbytes):
# first three bytes (six ascii hex chars)
plmn_str = sixhexbytes[:plmn_chars]
# 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
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['mnc'] = dec_mnc_from_plmn(plmn_str)
res['epdg_priority'] = epdg_priority_str
res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN'
return res
def format_ePDGSelection(hexstr):
ePDGSelection_info_tag_chars = 2
ePDGSelection_info_tag_str = hexstr[:2]
@ -1031,17 +1105,20 @@ def format_ePDGSelection(hexstr):
content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:]
# 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):
rec_info = dec_ePDGSelection(rec_data)
if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
rec_str = "unused"
else:
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)
return s
def get_addr_type(addr):
"""
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
def sw_match(sw: str, pattern: str) -> bool:
"""Match given SW against given pattern."""
# 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
return sw_masked == pattern
def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1,
align_left: bool = True) -> str:
"""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))
return '\n'.join(table)
def auto_int(x):
"""Helper function for argparse to accept hexadecimal integers."""
return int(x, 0)
class JsonEncoder(json.JSONEncoder):
"""Extend the standard library JSONEncoder with support for more types."""
def default(self, o):
if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray):
return b2h(o)
return json.JSONEncoder.default(self, o)
def boxed_heading_str(heading, width=80):
"""Generate a string that contains a boxed heading."""
# Auto-enlarge box if heading exceeds length
@ -1164,12 +1247,12 @@ def boxed_heading_str(heading, width=80):
return res
class DataObject(abc.ABC):
"""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
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."""
def __init__(self, name: str, desc: str = None, tag: int = None):
"""
Args:
@ -1236,7 +1319,8 @@ class DataObject(abc.ABC):
bytes remaining at end of 'do' after parsing one TLV/DO.
"""
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]
val = do[2:2+length]
self.from_bytes(val)
@ -1270,8 +1354,10 @@ class DataObject(abc.ABC):
def encode(self) -> bytes:
return self.to_tlv()
class TL0_DataObject(DataObject):
"""Data Object that has Tag, Len=0 and no Value part."""
def __init__(self, name: str, desc: str, tag: int, val=None):
super().__init__(name, desc, tag)
self.val = val
@ -1289,6 +1375,7 @@ class DataObjectCollection:
"""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
of each DO."""
def __init__(self, name: str, desc: str = None, members=None):
self.name = name
self.desc = desc
@ -1352,10 +1439,12 @@ class DataObjectCollection:
res.append(obj.to_tlv())
return res
class DataObjectChoice(DataObjectCollection):
"""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
on the tag."""
def __add__(self, other):
"""We overload the add operator here to avoid inheriting it from DataObjecCollection."""
raise TypeError
@ -1395,11 +1484,13 @@ class DataObjectChoice(DataObjectCollection):
obj = self.members_by_name(decoded[0])
return obj.to_tlv()
class DataObjectSequence:
"""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.
By wrapping them into this formal DataObjectSequence, we can offer convenience methods
for encoding or decoding an entire sequence."""
def __init__(self, name: str, desc: str = None, sequence=None):
self.sequence = sequence or []
self.name = name
@ -1469,8 +1560,10 @@ class DataObjectSequence:
i += 1
return encoded
class CardCommand:
"""A single card command / instruction."""
def __init__(self, name, ins, cla_list=None, desc=None):
self.name = name
self.ins = ins
@ -1503,6 +1596,7 @@ class CardCommand:
class CardCommandSet:
"""A set of card instructions, typically specified within one spec."""
def __init__(self, name, cmds=[]):
self.name = name
self.cmds = {c.ins: c for c in cmds}
@ -1523,7 +1617,8 @@ class CardCommandSet:
for c in other.cmds.keys():
self.cmds[c] = other.cmds[c]
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):
"""look-up the command within the CommandSet."""
@ -1535,6 +1630,7 @@ class CardCommandSet:
return None
return cmd
def all_subclasses(cls) -> set:
"""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)])