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

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

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

View File

@@ -41,6 +41,7 @@ from pySim.ts_51_011 import EF, EF_AD
from pySim.card_handler import * from pySim.card_handler import *
from pySim.utils import * from pySim.utils import *
def parse_options(): def parse_options():
parser = OptionParser(usage="usage: %prog [options]") parser = OptionParser(usage="usage: %prog [options]")
@@ -190,7 +191,6 @@ def parse_options():
parser.add_option("--read-csv", dest="read_csv", metavar="FILE", parser.add_option("--read-csv", dest="read_csv", metavar="FILE",
help="Read parameters from CSV file rather than command line") help="Read parameters from CSV file rather than command line")
parser.add_option("--write-csv", dest="write_csv", metavar="FILE", parser.add_option("--write-csv", dest="write_csv", metavar="FILE",
help="Append generated parameters in CSV file", help="Append generated parameters in CSV file",
) )
@@ -215,12 +215,14 @@ def parse_options():
if options.source == 'csv': if options.source == 'csv':
if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False): if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False):
parser.error("CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode") parser.error(
"CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode")
if options.read_csv is None: if options.read_csv is None:
parser.error("CSV mode requires a CSV input file") parser.error("CSV mode requires a CSV input file")
elif options.source == 'cmdline': elif options.source == 'cmdline':
if ((options.imsi is None) or (options.iccid is None)) and (options.num is None): if ((options.imsi is None) or (options.iccid is None)) and (options.num is None):
parser.error("If either IMSI or ICCID isn't specified, num is required") parser.error(
"If either IMSI or ICCID isn't specified, num is required")
else: else:
parser.error("Only `cmdline' and `csv' sources supported") parser.error("Only `cmdline' and `csv' sources supported")
@@ -232,7 +234,8 @@ def parse_options():
if (options.batch_mode): if (options.batch_mode):
if (options.imsi is not None) or (options.iccid is not None): if (options.imsi is not None) or (options.iccid is not None):
parser.error("Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more informations") parser.error(
"Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more information")
if args: if args:
parser.error("Extraneous arguments") parser.error("Extraneous arguments")
@@ -246,18 +249,22 @@ def _digits(secret, usage, len, num):
d = ''.join(['%02d' % x for x in s.digest()]) d = ''.join(['%02d' % x for x in s.digest()])
return d[0:len] return d[0:len]
def _mcc_mnc_digits(mcc, mnc): def _mcc_mnc_digits(mcc, mnc):
return '%s%s' % (mcc, mnc) return '%s%s' % (mcc, mnc)
def _cc_digits(cc): def _cc_digits(cc):
return ('%03d' if cc > 100 else '%02d') % cc return ('%03d' if cc > 100 else '%02d') % cc
def _isnum(s, l=-1): def _isnum(s, l=-1):
return s.isdigit() and ((l== -1) or (len(s) == l)) return s.isdigit() and ((l == -1) or (len(s) == l))
def _ishex(s, l=-1): def _ishex(s, l=-1):
hc = '0123456789abcdef' hc = '0123456789abcdef'
return all([x in hc for x in s.lower()]) and ((l== -1) or (len(s) == l)) return all([x in hc for x in s.lower()]) and ((l == -1) or (len(s) == l))
def _dbi_binary_quote(s): def _dbi_binary_quote(s):
@@ -272,7 +279,8 @@ def _dbi_binary_quote(s):
for i in range(1, 256): for i in range(1, 256):
if i == 39: if i == 39:
continue continue
sum_ = cnt.get(i, 0) + cnt.get((i+1)&0xff, 0) + cnt.get((i+39)&0xff, 0) sum_ = cnt.get(i, 0) + cnt.get((i+1) & 0xff, 0) + \
cnt.get((i+39) & 0xff, 0)
if sum_ < m: if sum_ < m:
m = sum_ m = sum_
e = i e = i
@@ -281,7 +289,7 @@ def _dbi_binary_quote(s):
# Generate output # Generate output
out = [] out = []
out.append( chr(e) ) # Offset out.append(chr(e)) # Offset
for c in s: for c in s:
x = (256 + ord(c) - e) % 256 x = (256 + ord(c) - e) % 256
if x in (0, 1, 39): if x in (0, 1, 39):
@@ -292,6 +300,7 @@ def _dbi_binary_quote(s):
return ''.join(out) return ''.join(out)
def gen_parameters(opts): def gen_parameters(opts):
"""Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the """Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the
options given by the user""" options given by the user"""
@@ -331,7 +340,8 @@ def gen_parameters(opts):
'Start with \'+\' for international numbers.') 'Start with \'+\' for international numbers.')
if len(msisdn) > 10 * 2: if len(msisdn) > 10 * 2:
# TODO: Support MSISDN of length > 20 (10 Bytes) # TODO: Support MSISDN of length > 20 (10 Bytes)
raise ValueError('MSISDNs longer than 20 digits are not (yet) supported.') raise ValueError(
'MSISDNs longer than 20 digits are not (yet) supported.')
# ICCID (19 digits, E.118), though some phase1 vendors use 20 :( # ICCID (19 digits, E.118), though some phase1 vendors use 20 :(
if opts.iccid is not None: if opts.iccid is not None:
@@ -406,7 +416,8 @@ def gen_parameters(opts):
else: else:
smsc = '00%d' % opts.country + '5555' # Hack ... smsc = '00%d' % opts.country + '5555' # Hack ...
smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + swap_nibbles(rpad(smsc, 20)) smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + \
swap_nibbles(rpad(smsc, 20))
smsp = ( smsp = (
'e1' + # Parameters indicator 'e1' + # Parameters indicator
@@ -434,7 +445,7 @@ def gen_parameters(opts):
if not re.match('^[0-9a-fA-F]{32}$', ki): if not re.match('^[0-9a-fA-F]{32}$', ki):
raise ValueError('Ki needs to be 128 bits, in hex format') raise ValueError('Ki needs to be 128 bits, in hex format')
else: else:
ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]) ki = ''.join(['%02x' % random.randrange(0, 256) for i in range(16)])
# OPC (random) # OPC (random)
if opts.opc is not None: if opts.opc is not None:
@@ -445,7 +456,7 @@ def gen_parameters(opts):
elif opts.op is not None: elif opts.op is not None:
opc = derive_milenage_opc(ki, opts.op) opc = derive_milenage_opc(ki, opts.op)
else: else:
opc = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]) opc = ''.join(['%02x' % random.randrange(0, 256) for i in range(16)])
pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex) pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
@@ -456,27 +467,28 @@ def gen_parameters(opts):
epdg_mcc = opts.epdgSelection[:3] epdg_mcc = opts.epdgSelection[:3]
epdg_mnc = opts.epdgSelection[3:] epdg_mnc = opts.epdgSelection[3:]
if not epdg_mcc.isdigit() or not epdg_mnc.isdigit(): if not epdg_mcc.isdigit() or not epdg_mnc.isdigit():
raise ValueError('PLMN for ePDG Selection must only contain decimal digits') raise ValueError(
'PLMN for ePDG Selection must only contain decimal digits')
# Return that # Return that
return { return {
'name' : opts.name, 'name': opts.name,
'iccid' : iccid, 'iccid': iccid,
'mcc' : mcc, 'mcc': mcc,
'mnc' : mnc, 'mnc': mnc,
'imsi' : imsi, 'imsi': imsi,
'smsp' : smsp, 'smsp': smsp,
'ki' : ki, 'ki': ki,
'opc' : opc, 'opc': opc,
'acc' : acc, 'acc': acc,
'pin_adm' : pin_adm, 'pin_adm': pin_adm,
'msisdn' : opts.msisdn, 'msisdn': opts.msisdn,
'epdgid' : opts.epdgid, 'epdgid': opts.epdgid,
'epdgSelection' : opts.epdgSelection, 'epdgSelection': opts.epdgSelection,
'pcscf' : opts.pcscf, 'pcscf': opts.pcscf,
'ims_hdomain': opts.ims_hdomain, 'ims_hdomain': opts.ims_hdomain,
'impi' : opts.impi, 'impi': opts.impi,
'impu' : opts.impu, 'impu': opts.impu,
'opmode': opts.opmode, 'opmode': opts.opmode,
} }
@@ -511,13 +523,14 @@ def write_params_csv(opts, params):
cw.writerow([params[x] for x in row]) cw.writerow([params[x] for x in row])
f.close() f.close()
def _read_params_csv(opts, iccid=None, imsi=None): def _read_params_csv(opts, iccid=None, imsi=None):
import csv import csv
f = open(opts.read_csv, 'r') f = open(opts.read_csv, 'r')
cr = csv.DictReader(f) cr = csv.DictReader(f)
# Lower-case fieldnames # Lower-case fieldnames
cr.fieldnames = [ field.lower() for field in cr.fieldnames ] cr.fieldnames = [field.lower() for field in cr.fieldnames]
i = 0 i = 0
if not 'iccid' in cr.fieldnames: if not 'iccid' in cr.fieldnames:
@@ -539,6 +552,7 @@ def _read_params_csv(opts, iccid=None, imsi=None):
f.close() f.close()
return None return None
def read_params_csv(opts, imsi=None, iccid=None): def read_params_csv(opts, imsi=None, iccid=None):
row = _read_params_csv(opts, iccid=iccid, imsi=imsi) row = _read_params_csv(opts, iccid=iccid, imsi=imsi)
if row is not None: if row is not None:
@@ -548,10 +562,10 @@ def read_params_csv(opts, imsi=None, iccid=None):
pin_adm = None pin_adm = None
# We need to escape the pin_adm we get from the csv # We need to escape the pin_adm we get from the csv
if 'pin_adm' in row: if 'pin_adm' in row:
pin_adm = ''.join(['%02x'%(ord(x)) for x in row['pin_adm']]) pin_adm = ''.join(['%02x' % (ord(x)) for x in row['pin_adm']])
# Stay compatible to the odoo csv format # Stay compatible to the odoo csv format
elif 'adm1' in row: elif 'adm1' in row:
pin_adm = ''.join(['%02x'%(ord(x)) for x in row['adm1']]) pin_adm = ''.join(['%02x' % (ord(x)) for x in row['adm1']])
if pin_adm: if pin_adm:
row['pin_adm'] = rpad(pin_adm, 16) row['pin_adm'] = rpad(pin_adm, 16)
@@ -565,9 +579,11 @@ def read_params_csv(opts, imsi=None, iccid=None):
try: try:
try_encode = h2b(pin_adm) try_encode = h2b(pin_adm)
except ValueError: except ValueError:
raise ValueError("pin_adm_hex needs to be hex encoded using this option") raise ValueError(
"pin_adm_hex needs to be hex encoded using this option")
else: else:
raise ValueError("pin_adm_hex needs to be exactly 16 digits (hex encoded)") raise ValueError(
"pin_adm_hex needs to be exactly 16 digits (hex encoded)")
return row return row
@@ -597,20 +613,23 @@ def write_params_hlr(opts, params):
'(subscriber_id, algorithm_id, a3a8_ki)' + '(subscriber_id, algorithm_id, a3a8_ki)' +
'VALUES ' + 'VALUES ' +
'(?,?,?)', '(?,?,?)',
[ sub_id, 2, sqlite3.Binary(_dbi_binary_quote(h2b(params['ki']))) ], [sub_id, 2, sqlite3.Binary(
_dbi_binary_quote(h2b(params['ki'])))],
) )
conn.commit() conn.commit()
conn.close() conn.close()
def write_parameters(opts, params): def write_parameters(opts, params):
write_params_csv(opts, params) write_params_csv(opts, params)
write_params_hlr(opts, params) write_params_hlr(opts, params)
BATCH_STATE = [ 'name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num' ] BATCH_STATE = ['name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num']
BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki'] BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki']
def init_batch(opts): def init_batch(opts):
# Need to do something ? # Need to do something ?
if not opts.batch_mode: if not opts.batch_mode:
@@ -634,7 +653,7 @@ def init_batch(opts):
d = json.loads(fh.read()) d = json.loads(fh.read())
fh.close() fh.close()
for k,v in d.iteritems(): for k, v in d.iteritems():
setattr(opts, k, v) setattr(opts, k, v)
@@ -643,7 +662,7 @@ def save_batch(opts):
if not opts.batch_mode or not opts.batch_state: if not opts.batch_mode or not opts.batch_state:
return return
d = json.dumps(dict([(k,getattr(opts,k)) for k in BATCH_STATE])) d = json.dumps(dict([(k, getattr(opts, k)) for k in BATCH_STATE]))
fh = open(opts.batch_state, 'w') fh = open(opts.batch_state, 'w')
fh.write(d) fh.write(d)
fh.close() fh.close()
@@ -682,13 +701,13 @@ def process_card(opts, first, ch):
if opts.dry_run: if opts.dry_run:
# Connect transport # Connect transport
ch.get(False) ch.get(False)
(res,_) = scc.read_binary(['3f00', '2fe2'], length=10) (res, _) = scc.read_binary(['3f00', '2fe2'], length=10)
iccid = dec_iccid(res) iccid = dec_iccid(res)
elif opts.read_imsi: elif opts.read_imsi:
if opts.dry_run: if opts.dry_run:
# Connect transport # Connect transport
ch.get(False) ch.get(False)
(res,_) = scc.read_binary(EF['IMSI']) (res, _) = scc.read_binary(EF['IMSI'])
imsi = swap_nibbles(res)[3:] imsi = swap_nibbles(res)[3:]
else: else:
imsi = opts.imsi imsi = opts.imsi

View File

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

View File

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

View File

@@ -1 +0,0 @@

View File

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

View File

@@ -30,13 +30,14 @@ import subprocess
import sys import sys
import yaml import yaml
class CardHandlerBase: class CardHandlerBase:
"""Abstract base class representing a mechanism for card insertion/removal.""" """Abstract base class representing a mechanism for card insertion/removal."""
def __init__(self, sl:LinkBase): def __init__(self, sl: LinkBase):
self.sl = sl self.sl = sl
def get(self, first:bool = False): def get(self, first: bool = False):
"""Method called when pySim needs a new card to be inserted. """Method called when pySim needs a new card to be inserted.
Args: Args:
@@ -60,7 +61,7 @@ class CardHandlerBase:
print("Programming successful: ", end='') print("Programming successful: ", end='')
self._done() self._done()
def _get(self, first:bool = False): def _get(self, first: bool = False):
pass pass
def _error(self): def _error(self):
@@ -73,7 +74,7 @@ class CardHandlerBase:
class CardHandler(CardHandlerBase): class CardHandler(CardHandlerBase):
"""Manual card handler: User is prompted to insert/remove card from the reader.""" """Manual card handler: User is prompted to insert/remove card from the reader."""
def _get(self, first:bool = False): def _get(self, first: bool = False):
print("Insert card now (or CTRL-C to cancel)") print("Insert card now (or CTRL-C to cancel)")
self.sl.wait_for_card(newcardonly=not first) self.sl.wait_for_card(newcardonly=not first)
@@ -91,7 +92,7 @@ class CardHandlerAuto(CardHandlerBase):
verbose = True verbose = True
def __init__(self, sl:LinkBase, config_file:str): def __init__(self, sl: LinkBase, config_file: str):
super().__init__(sl) super().__init__(sl)
print("Card handler Config-file: " + str(config_file)) print("Card handler Config-file: " + str(config_file))
with open(config_file) as cfg: with open(config_file) as cfg:
@@ -116,7 +117,8 @@ class CardHandlerAuto(CardHandlerBase):
def __exec_cmd(self, command): def __exec_cmd(self, command):
print("Card handler Commandline: " + str(command)) print("Card handler Commandline: " + str(command))
proc = subprocess.Popen([command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) proc = subprocess.Popen(
[command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
out = proc.communicate() out = proc.communicate()
rc = proc.returncode rc = proc.returncode
@@ -128,7 +130,7 @@ class CardHandlerAuto(CardHandlerBase):
print("Error: Card handler failure! (rc=" + str(rc) + ")") print("Error: Card handler failure! (rc=" + str(rc) + ")")
sys.exit(rc) sys.exit(rc)
def _get(self, first:bool = False): def _get(self, first: bool = False):
print("Transporting card into the reader-bay...") print("Transporting card into the reader-bay...")
self.__exec_cmd(self.cmds['get']) self.__exec_cmd(self.cmds['get'])
if self.sl: if self.sl:

View File

@@ -35,13 +35,15 @@ import csv
card_key_providers = [] # type: List['CardKeyProvider'] card_key_providers = [] # type: List['CardKeyProvider']
class CardKeyProvider(abc.ABC): class CardKeyProvider(abc.ABC):
"""Base class, not containing any concrete implementation.""" """Base class, not containing any concrete implementation."""
VALID_FIELD_NAMES = ['ICCID', 'ADM1', 'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2'] VALID_FIELD_NAMES = ['ICCID', 'ADM1',
'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2']
# check input parameters, but do nothing concrete yet # check input parameters, but do nothing concrete yet
def _verify_get_data(self, fields:List[str]=[], key:str='ICCID', value:str="") -> Dict[str,str]: def _verify_get_data(self, fields: List[str] = [], key: str = 'ICCID', value: str = "") -> Dict[str, str]:
"""Verify multiple fields for identified card. """Verify multiple fields for identified card.
Args: Args:
@@ -62,14 +64,14 @@ class CardKeyProvider(abc.ABC):
return {} return {}
def get_field(self, field:str, key:str='ICCID', value:str="") -> Optional[str]: def get_field(self, field: str, key: str = 'ICCID', value: str = "") -> Optional[str]:
"""get a single field from CSV file using a specified key/value pair""" """get a single field from CSV file using a specified key/value pair"""
fields = [field] fields = [field]
result = self.get(fields, key, value) result = self.get(fields, key, value)
return result.get(field) return result.get(field)
@abc.abstractmethod @abc.abstractmethod
def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]: def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
"""Get multiple card-individual fields for identified card. """Get multiple card-individual fields for identified card.
Args: Args:
@@ -80,12 +82,13 @@ class CardKeyProvider(abc.ABC):
dictionary of {field, value} strings for each requested field from 'fields' dictionary of {field, value} strings for each requested field from 'fields'
""" """
class CardKeyProviderCsv(CardKeyProvider): class CardKeyProviderCsv(CardKeyProvider):
"""Card key provider implementation that allows to query against a specified CSV file""" """Card key provider implementation that allows to query against a specified CSV file"""
csv_file = None csv_file = None
filename = None filename = None
def __init__(self, filename:str): def __init__(self, filename: str):
""" """
Args: Args:
filename : file name (path) of CSV file containing card-individual key/data filename : file name (path) of CSV file containing card-individual key/data
@@ -95,28 +98,29 @@ class CardKeyProviderCsv(CardKeyProvider):
raise RuntimeError("Could not open CSV file '%s'" % filename) raise RuntimeError("Could not open CSV file '%s'" % filename)
self.filename = filename self.filename = filename
def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]: def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]:
super()._verify_get_data(fields, key, value) super()._verify_get_data(fields, key, value)
self.csv_file.seek(0) self.csv_file.seek(0)
cr = csv.DictReader(self.csv_file) cr = csv.DictReader(self.csv_file)
if not cr: if not cr:
raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename) raise RuntimeError(
cr.fieldnames = [ field.upper() for field in cr.fieldnames ] "Could not open DictReader for CSV-File '%s'" % self.filename)
cr.fieldnames = [field.upper() for field in cr.fieldnames]
rc = {} rc = {}
for row in cr: for row in cr:
if row[key] == value: if row[key] == value:
for f in fields: for f in fields:
if f in row: if f in row:
rc.update({f : row[f]}) rc.update({f: row[f]})
else: else:
raise RuntimeError("CSV-File '%s' lacks column '%s'" % raise RuntimeError("CSV-File '%s' lacks column '%s'" %
(self.filename, f)) (self.filename, f))
return rc return rc
def card_key_provider_register(provider:CardKeyProvider, provider_list=card_key_providers): def card_key_provider_register(provider: CardKeyProvider, provider_list=card_key_providers):
"""Register a new card key provider. """Register a new card key provider.
Args: Args:
@@ -128,7 +132,7 @@ def card_key_provider_register(provider:CardKeyProvider, provider_list=card_key_
provider_list.append(provider) provider_list.append(provider)
def card_key_provider_get(fields, key:str, value:str, provider_list=card_key_providers) -> Dict[str,str]: def card_key_provider_get(fields, key: str, value: str, provider_list=card_key_providers) -> Dict[str, str]:
"""Query all registered card data providers for card-individual [key] data. """Query all registered card data providers for card-individual [key] data.
Args: Args:
@@ -141,14 +145,15 @@ def card_key_provider_get(fields, key:str, value:str, provider_list=card_key_pro
""" """
for p in provider_list: for p in provider_list:
if not isinstance(p, CardKeyProvider): if not isinstance(p, CardKeyProvider):
raise ValueError("provider list contains element which is not a card data provier") raise ValueError(
"provider list contains element which is not a card data provier")
result = p.get(fields, key, value) result = p.get(fields, key, value)
if result: if result:
return result return result
return {} return {}
def card_key_provider_get_field(field:str, key:str, value:str, provider_list=card_key_providers) -> Optional[str]: def card_key_provider_get_field(field: str, key: str, value: str, provider_list=card_key_providers) -> Optional[str]:
"""Query all registered card data providers for a single field. """Query all registered card data providers for a single field.
Args: Args:
@@ -161,7 +166,8 @@ def card_key_provider_get_field(field:str, key:str, value:str, provider_list=car
""" """
for p in provider_list: for p in provider_list:
if not isinstance(p, CardKeyProvider): if not isinstance(p, CardKeyProvider):
raise ValueError("provider list contains element which is not a card data provier") raise ValueError(
"provider list contains element which is not a card data provier")
result = p.get_field(field, key, value) result = p.get_field(field, key, value)
if result: if result:
return result return result

View File

@@ -32,7 +32,8 @@ from pySim.utils import *
from smartcard.util import toBytes from smartcard.util import toBytes
from pytlv.TLV import * from pytlv.TLV import *
def format_addr(addr:str, addr_type:str) -> str:
def format_addr(addr: str, addr_type: str) -> str:
""" """
helper function to format an FQDN (addr_type = '00') or IPv4 helper function to format an FQDN (addr_type = '00') or IPv4
(addr_type = '01') address string into a printable string that (addr_type = '01') address string into a printable string that
@@ -40,9 +41,9 @@ def format_addr(addr:str, addr_type:str) -> str:
string (addr) string (addr)
""" """
res = "" res = ""
if addr_type == '00': #FQDN if addr_type == '00': # FQDN
res += "\t%s # %s\n" % (s2h(addr), addr) res += "\t%s # %s\n" % (s2h(addr), addr)
elif addr_type == '01': #IPv4 elif addr_type == '01': # IPv4
octets = addr.split(".") octets = addr.split(".")
addr_hex = "" addr_hex = ""
for o in octets: for o in octets:
@@ -50,6 +51,7 @@ def format_addr(addr:str, addr_type:str) -> str:
res += "\t%s # %s\n" % (addr_hex, addr) res += "\t%s # %s\n" % (addr_hex, addr)
return res return res
class SimCard(object): class SimCard(object):
name = 'SIM' name = 'SIM'
@@ -126,7 +128,8 @@ class SimCard(object):
size = len(data[0]) // 2 size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc) hplmn = enc_plmn(mcc, mnc)
content = hplmn + access_tech content = hplmn + access_tech
data, sw = self._scc.update_binary(EF['HPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) data, sw = self._scc.update_binary(
EF['HPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
return sw return sw
def read_oplmn_act(self): def read_oplmn_act(self):
@@ -142,7 +145,8 @@ class SimCard(object):
size = len(data[0]) // 2 size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc) hplmn = enc_plmn(mcc, mnc)
content = hplmn + access_tech content = hplmn + access_tech
data, sw = self._scc.update_binary(EF['OPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) data, sw = self._scc.update_binary(
EF['OPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
return sw return sw
def read_plmn_act(self): def read_plmn_act(self):
@@ -158,14 +162,16 @@ class SimCard(object):
size = len(data[0]) // 2 size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc) hplmn = enc_plmn(mcc, mnc)
content = hplmn + access_tech content = hplmn + access_tech
data, sw = self._scc.update_binary(EF['PLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) data, sw = self._scc.update_binary(
EF['PLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1))
return sw return sw
def update_plmnsel(self, mcc, mnc): def update_plmnsel(self, mcc, mnc):
data = self._scc.read_binary(EF['PLMNsel'], length=None, offset=0) data = self._scc.read_binary(EF['PLMNsel'], length=None, offset=0)
size = len(data[0]) // 2 size = len(data[0]) // 2
hplmn = enc_plmn(mcc, mnc) hplmn = enc_plmn(mcc, mnc)
data, sw = self._scc.update_binary(EF['PLMNsel'], hplmn + 'ff' * (size-3)) data, sw = self._scc.update_binary(
EF['PLMNsel'], hplmn + 'ff' * (size-3))
return sw return sw
def update_smsp(self, smsp): def update_smsp(self, smsp):
@@ -193,7 +199,8 @@ class SimCard(object):
ad = EF_AD() ad = EF_AD()
# read from card # read from card
raw_hex_data, sw = self._scc.read_binary(EF['AD'], length=None, offset=0) raw_hex_data, sw = self._scc.read_binary(
EF['AD'], length=None, offset=0)
abstract_data = ad.decode_hex(raw_hex_data) abstract_data = ad.decode_hex(raw_hex_data)
# perform updates # perform updates
@@ -231,9 +238,9 @@ class SimCard(object):
def update_spn(self, name="", show_in_hplmn=False, hide_in_oplmn=False): def update_spn(self, name="", show_in_hplmn=False, hide_in_oplmn=False):
abstract_data = { abstract_data = {
'hide_in_oplmn' : hide_in_oplmn, 'hide_in_oplmn': hide_in_oplmn,
'show_in_hplmn' : show_in_hplmn, 'show_in_hplmn': show_in_hplmn,
'spn' : name, 'spn': name,
} }
content = EF_SPN().encode_hex(abstract_data) content = EF_SPN().encode_hex(abstract_data)
data, sw = self._scc.update_binary(EF['SPN'], content) data, sw = self._scc.update_binary(EF['SPN'], content)
@@ -321,7 +328,8 @@ class SimCard(object):
def erase_record(self, ef, rec_no): def erase_record(self, ef, rec_no):
"""Erase the contents of a single record""" """Erase the contents of a single record"""
len = self._scc.record_size(ef) len = self._scc.record_size(ef)
self._scc.update_record(ef, rec_no, "ff" * len, force_len=False, verify=True) self._scc.update_record(ef, rec_no, "ff" * len,
force_len=False, verify=True)
def set_apdu_parameter(self, cla, sel_ctrl): def set_apdu_parameter(self, cla, sel_ctrl):
"""Set apdu parameters (class byte and selection control bytes)""" """Set apdu parameters (class byte and selection control bytes)"""
@@ -332,6 +340,7 @@ class SimCard(object):
"""Get apdu parameters (class byte and selection control bytes)""" """Get apdu parameters (class byte and selection control bytes)"""
return (self._scc.cla_byte, self._scc.sel_ctrl) return (self._scc.cla_byte, self._scc.sel_ctrl)
class UsimCard(SimCard): class UsimCard(SimCard):
name = 'USIM' name = 'USIM'
@@ -347,7 +356,8 @@ class UsimCard(SimCard):
return (None, sw) return (None, sw)
def update_ehplmn(self, mcc, mnc): def update_ehplmn(self, mcc, mnc):
data = self._scc.read_binary(EF_USIM_ADF_map['EHPLMN'], length=None, offset=0) data = self._scc.read_binary(
EF_USIM_ADF_map['EHPLMN'], length=None, offset=0)
size = len(data[0]) // 2 size = len(data[0]) // 2
ehplmn = enc_plmn(mcc, mnc) ehplmn = enc_plmn(mcc, mnc)
data, sw = self._scc.update_binary(EF_USIM_ADF_map['EHPLMN'], ehplmn) data, sw = self._scc.update_binary(EF_USIM_ADF_map['EHPLMN'], ehplmn)
@@ -370,7 +380,8 @@ class UsimCard(SimCard):
if len(epdgid) > 0: if len(epdgid) > 0:
addr_type = get_addr_type(epdgid) addr_type = get_addr_type(epdgid)
if addr_type == None: if addr_type == None:
raise ValueError("Unknown ePDG Id address type or invalid address provided") raise ValueError(
"Unknown ePDG Id address type or invalid address provided")
epdgid_tlv = rpad(enc_addr_tlv(epdgid, ('%02x' % addr_type)), size) epdgid_tlv = rpad(enc_addr_tlv(epdgid, ('%02x' % addr_type)), size)
else: else:
epdgid_tlv = rpad('ff', size) epdgid_tlv = rpad('ff', size)
@@ -386,13 +397,16 @@ class UsimCard(SimCard):
return (None, sw) return (None, sw)
def update_ePDGSelection(self, mcc, mnc): def update_ePDGSelection(self, mcc, mnc):
(res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGSelection'], length=None, offset=0) (res, sw) = self._scc.read_binary(
EF_USIM_ADF_map['ePDGSelection'], length=None, offset=0)
if sw == '9000' and (len(mcc) == 0 or len(mnc) == 0): if sw == '9000' and (len(mcc) == 0 or len(mnc) == 0):
# Reset contents # Reset contents
# 80 - Tag value # 80 - Tag value
(res, sw) = self._scc.update_binary(EF_USIM_ADF_map['ePDGSelection'], rpad('', len(res))) (res, sw) = self._scc.update_binary(
EF_USIM_ADF_map['ePDGSelection'], rpad('', len(res)))
elif sw == '9000': elif sw == '9000':
(res, sw) = self._scc.update_binary(EF_USIM_ADF_map['ePDGSelection'], enc_ePDGSelection(res, mcc, mnc)) (res, sw) = self._scc.update_binary(
EF_USIM_ADF_map['ePDGSelection'], enc_ePDGSelection(res, mcc, mnc))
return sw return sw
def read_ust(self): def read_ust(self):
@@ -407,9 +421,11 @@ class UsimCard(SimCard):
(res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST']) (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST'])
if sw == '9000': if sw == '9000':
content = enc_st(res, service, bit) content = enc_st(res, service, bit)
(res, sw) = self._scc.update_binary(EF_USIM_ADF_map['UST'], content) (res, sw) = self._scc.update_binary(
EF_USIM_ADF_map['UST'], content)
return sw return sw
class IsimCard(SimCard): class IsimCard(SimCard):
name = 'ISIM' name = 'ISIM'
@@ -429,29 +445,33 @@ class IsimCard(SimCard):
addr = None addr = None
addr_type = None addr_type = None
content = format_addr(addr, addr_type) content = format_addr(addr, addr_type)
pcscf_recs += "%s" % (len(content) and content or '\tNot available\n') pcscf_recs += "%s" % (len(content)
and content or '\tNot available\n')
else: else:
pcscf_recs += "\tP-CSCF: Can't read, response code = %s\n" % (sw) pcscf_recs += "\tP-CSCF: Can't read, response code = %s\n" % (
sw)
return pcscf_recs return pcscf_recs
def update_pcscf(self, pcscf): def update_pcscf(self, pcscf):
if len(pcscf) > 0: if len(pcscf) > 0:
addr_type = get_addr_type(pcscf) addr_type = get_addr_type(pcscf)
if addr_type == None: if addr_type == None:
raise ValueError("Unknown PCSCF address type or invalid address provided") raise ValueError(
"Unknown PCSCF address type or invalid address provided")
content = enc_addr_tlv(pcscf, ('%02x' % addr_type)) content = enc_addr_tlv(pcscf, ('%02x' % addr_type))
else: else:
# Just the tag value # Just the tag value
content = '80' content = '80'
rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['PCSCF']) rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['PCSCF'])
pcscf_tlv = rpad(content, rec_size_bytes*2) pcscf_tlv = rpad(content, rec_size_bytes*2)
data, sw = self._scc.update_record(EF_ISIM_ADF_map['PCSCF'], 1, pcscf_tlv) data, sw = self._scc.update_record(
EF_ISIM_ADF_map['PCSCF'], 1, pcscf_tlv)
return sw return sw
def read_domain(self): def read_domain(self):
(res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['DOMAIN']) (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['DOMAIN'])
if sw == '9000': if sw == '9000':
# Skip the inital tag value ('80') byte and get length of contents # Skip the initial tag value ('80') byte and get length of contents
length = int(res[2:4], 16) length = int(res[2:4], 16)
content = h2s(res[4:4+(length*2)]) content = h2s(res[4:4+(length*2)])
return (content, sw) return (content, sw)
@@ -472,13 +492,14 @@ class IsimCard(SimCard):
content = tlv.build({'80': hex_str}) content = tlv.build({'80': hex_str})
bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['DOMAIN']) bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['DOMAIN'])
data, sw = self._scc.update_binary(EF_ISIM_ADF_map['DOMAIN'], rpad(content, bin_size_bytes*2)) data, sw = self._scc.update_binary(
EF_ISIM_ADF_map['DOMAIN'], rpad(content, bin_size_bytes*2))
return sw return sw
def read_impi(self): def read_impi(self):
(res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IMPI']) (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IMPI'])
if sw == '9000': if sw == '9000':
# Skip the inital tag value ('80') byte and get length of contents # Skip the initial tag value ('80') byte and get length of contents
length = int(res[2:4], 16) length = int(res[2:4], 16)
content = h2s(res[4:4+(length*2)]) content = h2s(res[4:4+(length*2)])
return (content, sw) return (content, sw)
@@ -494,7 +515,8 @@ class IsimCard(SimCard):
content = tlv.build({'80': hex_str}) content = tlv.build({'80': hex_str})
bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['IMPI']) bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['IMPI'])
data, sw = self._scc.update_binary(EF_ISIM_ADF_map['IMPI'], rpad(content, bin_size_bytes*2)) data, sw = self._scc.update_binary(
EF_ISIM_ADF_map['IMPI'], rpad(content, bin_size_bytes*2))
return sw return sw
def read_impu(self): def read_impu(self):
@@ -503,12 +525,14 @@ class IsimCard(SimCard):
for i in range(0, rec_cnt): for i in range(0, rec_cnt):
(res, sw) = self._scc.read_record(EF_ISIM_ADF_map['IMPU'], i + 1) (res, sw) = self._scc.read_record(EF_ISIM_ADF_map['IMPU'], i + 1)
if sw == '9000': if sw == '9000':
# Skip the inital tag value ('80') byte and get length of contents # Skip the initial tag value ('80') byte and get length of contents
length = int(res[2:4], 16) length = int(res[2:4], 16)
content = h2s(res[4:4+(length*2)]) content = h2s(res[4:4+(length*2)])
impu_recs += "\t%s\n" % (len(content) and content or 'Not available') impu_recs += "\t%s\n" % (len(content)
and content or 'Not available')
else: else:
impu_recs += "IMS public user identity: Can't read, response code = %s\n" % (sw) impu_recs += "IMS public user identity: Can't read, response code = %s\n" % (
sw)
return impu_recs return impu_recs
def update_impu(self, impu=None): def update_impu(self, impu=None):
@@ -521,23 +545,28 @@ class IsimCard(SimCard):
rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['IMPU']) rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['IMPU'])
impu_tlv = rpad(content, rec_size_bytes*2) impu_tlv = rpad(content, rec_size_bytes*2)
data, sw = self._scc.update_record(EF_ISIM_ADF_map['IMPU'], 1, impu_tlv) data, sw = self._scc.update_record(
EF_ISIM_ADF_map['IMPU'], 1, impu_tlv)
return sw return sw
def read_iari(self): def read_iari(self):
rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['UICCIARI']) rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['UICCIARI'])
uiari_recs = "" uiari_recs = ""
for i in range(0, rec_cnt): for i in range(0, rec_cnt):
(res, sw) = self._scc.read_record(EF_ISIM_ADF_map['UICCIARI'], i + 1) (res, sw) = self._scc.read_record(
EF_ISIM_ADF_map['UICCIARI'], i + 1)
if sw == '9000': if sw == '9000':
# Skip the inital tag value ('80') byte and get length of contents # Skip the initial tag value ('80') byte and get length of contents
length = int(res[2:4], 16) length = int(res[2:4], 16)
content = h2s(res[4:4+(length*2)]) content = h2s(res[4:4+(length*2)])
uiari_recs += "\t%s\n" % (len(content) and content or 'Not available') uiari_recs += "\t%s\n" % (len(content)
and content or 'Not available')
else: else:
uiari_recs += "UICC IARI: Can't read, response code = %s\n" % (sw) uiari_recs += "UICC IARI: Can't read, response code = %s\n" % (
sw)
return uiari_recs return uiari_recs
class MagicSimBase(abc.ABC, SimCard): class MagicSimBase(abc.ABC, SimCard):
""" """
Theses cards uses several record based EFs to store the provider infos, Theses cards uses several record based EFs to store the provider infos,
@@ -558,7 +587,7 @@ class MagicSimBase(abc.ABC, SimCard):
""" """
_files = { } # type: Dict[str, Tuple[str, int, bool]] _files = {} # type: Dict[str, Tuple[str, int, bool]]
_ki_file = None # type: Optional[str] _ki_file = None # type: Optional[str]
@classmethod @classmethod
@@ -583,7 +612,7 @@ class MagicSimBase(abc.ABC, SimCard):
r = self._scc.select_path(['3f00', '7f4d', f[0]]) r = self._scc.select_path(['3f00', '7f4d', f[0]])
rec_len = int(r[-1][28:30], 16) rec_len = int(r[-1][28:30], 16)
tlen = int(r[-1][4:8],16) tlen = int(r[-1][4:8], 16)
rec_cnt = (tlen // rec_len) - 1 rec_cnt = (tlen // rec_len) - 1
if (rec_cnt < 1) or (rec_len != f[1]): if (rec_cnt < 1) or (rec_len != f[1]):
@@ -600,7 +629,8 @@ class MagicSimBase(abc.ABC, SimCard):
# Operator name ( 3f00/7f4d/8f0c ) # Operator name ( 3f00/7f4d/8f0c )
self._scc.update_record(self._files['name'][0], 2, self._scc.update_record(self._files['name'][0], 2,
rpad(b2h(p['name']), 32) + ('%02x' % len(p['name'])) + '01' rpad(b2h(p['name']), 32) + ('%02x' %
len(p['name'])) + '01'
) )
# ICCID/IMSI/Ki/HPLMN ( 3f00/7f4d/8f0d ) # ICCID/IMSI/Ki/HPLMN ( 3f00/7f4d/8f0d )
@@ -621,13 +651,13 @@ class MagicSimBase(abc.ABC, SimCard):
v += self._ki_file + '10' + p['ki'] v += self._ki_file + '10' + p['ki']
# PLMN_Sel # PLMN_Sel
v+= '6f30' + '18' + rpad(hplmn, 36) v += '6f30' + '18' + rpad(hplmn, 36)
# ACC # ACC
# This doesn't work with "fake" SuperSIM cards, # This doesn't work with "fake" SuperSIM cards,
# but will hopefully work with real SuperSIMs. # but will hopefully work with real SuperSIMs.
if p.get('acc') is not None: if p.get('acc') is not None:
v+= '6f78' + '02' + lpad(p['acc'], 4) v += '6f78' + '02' + lpad(p['acc'], 4)
self._scc.update_record(self._files['b_ef'][0], 1, self._scc.update_record(self._files['b_ef'][0], 1,
rpad(v, self._files['b_ef'][1]*2) rpad(v, self._files['b_ef'][1]*2)
@@ -655,7 +685,7 @@ class MagicSimBase(abc.ABC, SimCard):
df[v[0]] = (fv, ofs) df[v[0]] = (fv, ofs)
# Write # Write
for n in range(0,self._get_count()): for n in range(0, self._get_count()):
for k, (msg, ofs) in df.items(): for k, (msg, ofs) in df.items():
self._scc.update_record(['3f00', '7f4d', k], n + ofs, msg) self._scc.update_record(['3f00', '7f4d', k], n + ofs, msg)
@@ -665,9 +695,9 @@ class SuperSim(MagicSimBase):
name = 'supersim' name = 'supersim'
_files = { _files = {
'name' : ('8f0c', 18, True), 'name': ('8f0c', 18, True),
'b_ef' : ('8f0d', 74, True), 'b_ef': ('8f0d', 74, True),
'r_ef' : ('8f0e', 50, True), 'r_ef': ('8f0e', 50, True),
} }
_ki_file = None _ki_file = None
@@ -678,9 +708,9 @@ class MagicSim(MagicSimBase):
name = 'magicsim' name = 'magicsim'
_files = { _files = {
'name' : ('8f0c', 18, True), 'name': ('8f0c', 18, True),
'b_ef' : ('8f0d', 130, True), 'b_ef': ('8f0d', 130, True),
'r_ef' : ('8f0e', 102, False), 'r_ef': ('8f0e', 102, False),
} }
_ki_file = '6f1b' _ki_file = '6f1b'
@@ -713,7 +743,7 @@ class FakeMagicSim(SimCard):
r = self._scc.select_path(['3f00', '000c']) r = self._scc.select_path(['3f00', '000c'])
rec_len = int(r[-1][28:30], 16) rec_len = int(r[-1][28:30], 16)
tlen = int(r[-1][4:8],16) tlen = int(r[-1][4:8], 16)
rec_cnt = (tlen // rec_len) - 1 rec_cnt = (tlen // rec_len) - 1
if (rec_cnt < 1) or (rec_len != 0x5a): if (rec_cnt < 1) or (rec_len != 0x5a):
@@ -830,6 +860,7 @@ class SysmoSIMgr1(GrcardSim):
return None return None
return None return None
class SysmoUSIMgr1(UsimCard): class SysmoUSIMgr1(UsimCard):
""" """
sysmocom sysmoUSIM-GR1 sysmocom sysmoUSIM-GR1
@@ -845,10 +876,11 @@ class SysmoUSIMgr1(UsimCard):
# TODO: check if verify_chv could be used or what it needs # TODO: check if verify_chv could be used or what it needs
# self._scc.verify_chv(0x0A, [0x33,0x32,0x32,0x31,0x33,0x32,0x33,0x32]) # self._scc.verify_chv(0x0A, [0x33,0x32,0x32,0x31,0x33,0x32,0x33,0x32])
# Unlock the card.. # Unlock the card..
data, sw = self._scc._tp.send_apdu_checksw("0020000A083332323133323332") data, sw = self._scc._tp.send_apdu_checksw(
"0020000A083332323133323332")
# TODO: move into SimCardCommands # TODO: move into SimCardCommands
par = ( p['ki'] + # 16b K par = (p['ki'] + # 16b K
p['opc'] + # 32b OPC p['opc'] + # 32b OPC
enc_iccid(p['iccid']) + # 10b ICCID enc_iccid(p['iccid']) + # 10b ICCID
enc_imsi(p['imsi']) # 9b IMSI_len + id_type(9) + IMSI enc_imsi(p['imsi']) # 9b IMSI_len + id_type(9) + IMSI
@@ -941,7 +973,7 @@ class SysmoUSIMSJS1(UsimCard):
def __init__(self, ssc): def __init__(self, ssc):
super(SysmoUSIMSJS1, self).__init__(ssc) super(SysmoUSIMSJS1, self).__init__(ssc)
self._scc.cla_byte = "00" self._scc.cla_byte = "00"
self._scc.sel_ctrl = "0004" #request an FCP self._scc.sel_ctrl = "0004" # request an FCP
@classmethod @classmethod
def autodetect(kls, scc): def autodetect(kls, scc):
@@ -956,7 +988,8 @@ class SysmoUSIMSJS1(UsimCard):
def verify_adm(self, key): def verify_adm(self, key):
# authenticate as ADM using default key (written on the card..) # authenticate as ADM using default key (written on the card..)
if not key: if not key:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
(res, sw) = self._scc.verify_chv(0x0A, key) (res, sw) = self._scc.verify_chv(0x0A, key)
return sw return sw
@@ -994,25 +1027,25 @@ class SysmoUSIMSJS1(UsimCard):
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_plmnsel(p['mcc'], p['mnc']) sw = self.update_plmnsel(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming PLMNsel failed with code %s"%sw) print("Programming PLMNsel failed with code %s" % sw)
# EF.PLMNwAcT # EF.PLMNwAcT
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_plmn_act(p['mcc'], p['mnc']) sw = self.update_plmn_act(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming PLMNwAcT failed with code %s"%sw) print("Programming PLMNwAcT failed with code %s" % sw)
# EF.OPLMNwAcT # EF.OPLMNwAcT
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_oplmn_act(p['mcc'], p['mnc']) sw = self.update_oplmn_act(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming OPLMNwAcT failed with code %s"%sw) print("Programming OPLMNwAcT failed with code %s" % sw)
# EF.HPLMNwAcT # EF.HPLMNwAcT
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_hplmn_act(p['mcc'], p['mnc']) sw = self.update_hplmn_act(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming HPLMNwAcT failed with code %s"%sw) print("Programming HPLMNwAcT failed with code %s" % sw)
# EF.AD # EF.AD
if (p.get('mcc') and p.get('mnc')) or p.get('opmode'): if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
@@ -1022,12 +1055,13 @@ class SysmoUSIMSJS1(UsimCard):
mnc = None mnc = None
sw = self.update_ad(mnc=mnc, opmode=p.get('opmode')) sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'))
if sw != '9000': if sw != '9000':
print("Programming AD failed with code %s"%sw) print("Programming AD failed with code %s" % sw)
# EF.SMSP # EF.SMSP
if p.get('smsp'): if p.get('smsp'):
r = self._scc.select_path(['3f00', '7f10']) r = self._scc.select_path(['3f00', '7f10'])
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True) data, sw = self._scc.update_record(
'6f42', 1, lpad(p['smsp'], 104), force_len=True)
# EF.MSISDN # EF.MSISDN
# TODO: Alpha Identifier (currently 'ff'O * 20) # TODO: Alpha Identifier (currently 'ff'O * 20)
@@ -1069,7 +1103,6 @@ class FairwavesSIM(UsimCard):
self._adm_chv_num = 0x11 self._adm_chv_num = 0x11
self._adm2_chv_num = 0x12 self._adm2_chv_num = 0x12
@classmethod @classmethod
def autodetect(kls, scc): def autodetect(kls, scc):
try: try:
@@ -1080,7 +1113,6 @@ class FairwavesSIM(UsimCard):
return None return None
return None return None
def verify_adm2(self, key): def verify_adm2(self, key):
''' '''
Authenticate with ADM2 key. Authenticate with ADM2 key.
@@ -1093,7 +1125,6 @@ class FairwavesSIM(UsimCard):
(res, sw) = self._scc.verify_chv(self._adm2_chv_num, key) (res, sw) = self._scc.verify_chv(self._adm2_chv_num, key)
return sw return sw
def read_ki(self): def read_ki(self):
""" """
Read Ki in proprietary file. Read Ki in proprietary file.
@@ -1102,7 +1133,6 @@ class FairwavesSIM(UsimCard):
""" """
return self._scc.read_binary(self._EF['Ki']) return self._scc.read_binary(self._EF['Ki'])
def update_ki(self, ki): def update_ki(self, ki):
""" """
Set Ki in proprietary file. Set Ki in proprietary file.
@@ -1112,7 +1142,6 @@ class FairwavesSIM(UsimCard):
data, sw = self._scc.update_binary(self._EF['Ki'], ki) data, sw = self._scc.update_binary(self._EF['Ki'], ki)
return sw return sw
def read_op_opc(self): def read_op_opc(self):
""" """
Read Ki in proprietary file. Read Ki in proprietary file.
@@ -1123,7 +1152,6 @@ class FairwavesSIM(UsimCard):
type = 'OP' if ef[0:2] == '00' else 'OPC' type = 'OP' if ef[0:2] == '00' else 'OPC'
return ((type, ef[2:]), sw) return ((type, ef[2:]), sw)
def update_op(self, op): def update_op(self, op):
""" """
Set OP in proprietary file. Set OP in proprietary file.
@@ -1134,7 +1162,6 @@ class FairwavesSIM(UsimCard):
data, sw = self._scc.update_binary(self._EF['OP/OPC'], content) data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
return sw return sw
def update_opc(self, opc): def update_opc(self, opc):
""" """
Set OPC in proprietary file. Set OPC in proprietary file.
@@ -1166,35 +1193,37 @@ class FairwavesSIM(UsimCard):
def _program(self, p): def _program(self, p):
# authenticate as ADM1 # authenticate as ADM1
if not p['pin_adm']: if not p['pin_adm']:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
self.verify_adm(h2b(p['pin_adm'])) self.verify_adm(h2b(p['pin_adm']))
# TODO: Set operator name # TODO: Set operator name
if p.get('smsp') is not None: if p.get('smsp') is not None:
sw = self.update_smsp(p['smsp']) sw = self.update_smsp(p['smsp'])
if sw != '9000': if sw != '9000':
print("Programming SMSP failed with code %s"%sw) print("Programming SMSP failed with code %s" % sw)
# This SIM doesn't support changing ICCID # This SIM doesn't support changing ICCID
if p.get('mcc') is not None and p.get('mnc') is not None: if p.get('mcc') is not None and p.get('mnc') is not None:
sw = self.update_hplmn_act(p['mcc'], p['mnc']) sw = self.update_hplmn_act(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming MCC/MNC failed with code %s"%sw) print("Programming MCC/MNC failed with code %s" % sw)
if p.get('imsi') is not None: if p.get('imsi') is not None:
sw = self.update_imsi(p['imsi']) sw = self.update_imsi(p['imsi'])
if sw != '9000': if sw != '9000':
print("Programming IMSI failed with code %s"%sw) print("Programming IMSI failed with code %s" % sw)
if p.get('ki') is not None: if p.get('ki') is not None:
sw = self.update_ki(p['ki']) sw = self.update_ki(p['ki'])
if sw != '9000': if sw != '9000':
print("Programming Ki failed with code %s"%sw) print("Programming Ki failed with code %s" % sw)
if p.get('opc') is not None: if p.get('opc') is not None:
sw = self.update_opc(p['opc']) sw = self.update_opc(p['opc'])
if sw != '9000': if sw != '9000':
print("Programming OPC failed with code %s"%sw) print("Programming OPC failed with code %s" % sw)
if p.get('acc') is not None: if p.get('acc') is not None:
sw = self.update_acc(p['acc']) sw = self.update_acc(p['acc'])
if sw != '9000': if sw != '9000':
print("Programming ACC failed with code %s"%sw) print("Programming ACC failed with code %s" % sw)
class OpenCellsSim(SimCard): class OpenCellsSim(SimCard):
""" """
@@ -1208,7 +1237,6 @@ class OpenCellsSim(SimCard):
super(OpenCellsSim, self).__init__(ssc) super(OpenCellsSim, self).__init__(ssc)
self._adm_chv_num = 0x0A self._adm_chv_num = 0x0A
@classmethod @classmethod
def autodetect(kls, scc): def autodetect(kls, scc):
try: try:
@@ -1219,10 +1247,10 @@ class OpenCellsSim(SimCard):
return None return None
return None return None
def program(self, p): def program(self, p):
if not p['pin_adm']: if not p['pin_adm']:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
self._scc.verify_chv(0x0A, h2b(p['pin_adm'])) self._scc.verify_chv(0x0A, h2b(p['pin_adm']))
# select MF # select MF
@@ -1245,6 +1273,7 @@ class OpenCellsSim(SimCard):
# write EF.IMSI # write EF.IMSI
data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
class WavemobileSim(UsimCard): class WavemobileSim(UsimCard):
""" """
WavemobileSim WavemobileSim
@@ -1257,7 +1286,7 @@ class WavemobileSim(UsimCard):
super(WavemobileSim, self).__init__(ssc) super(WavemobileSim, self).__init__(ssc)
self._adm_chv_num = 0x0A self._adm_chv_num = 0x0A
self._scc.cla_byte = "00" self._scc.cla_byte = "00"
self._scc.sel_ctrl = "0004" #request an FCP self._scc.sel_ctrl = "0004" # request an FCP
@classmethod @classmethod
def autodetect(kls, scc): def autodetect(kls, scc):
@@ -1271,59 +1300,63 @@ class WavemobileSim(UsimCard):
def program(self, p): def program(self, p):
if not p['pin_adm']: if not p['pin_adm']:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
self.verify_adm(h2b(p['pin_adm'])) self.verify_adm(h2b(p['pin_adm']))
# EF.ICCID # EF.ICCID
# TODO: Add programming of the ICCID # TODO: Add programming of the ICCID
if p.get('iccid'): if p.get('iccid'):
print("Warning: Programming of the ICCID is not implemented for this type of card.") print(
"Warning: Programming of the ICCID is not implemented for this type of card.")
# KI (Presumably a propritary file) # KI (Presumably a propritary file)
# TODO: Add programming of KI # TODO: Add programming of KI
if p.get('ki'): if p.get('ki'):
print("Warning: Programming of the KI is not implemented for this type of card.") print(
"Warning: Programming of the KI is not implemented for this type of card.")
# OPc (Presumably a propritary file) # OPc (Presumably a propritary file)
# TODO: Add programming of OPc # TODO: Add programming of OPc
if p.get('opc'): if p.get('opc'):
print("Warning: Programming of the OPc is not implemented for this type of card.") print(
"Warning: Programming of the OPc is not implemented for this type of card.")
# EF.SMSP # EF.SMSP
if p.get('smsp'): if p.get('smsp'):
sw = self.update_smsp(p['smsp']) sw = self.update_smsp(p['smsp'])
if sw != '9000': if sw != '9000':
print("Programming SMSP failed with code %s"%sw) print("Programming SMSP failed with code %s" % sw)
# EF.IMSI # EF.IMSI
if p.get('imsi'): if p.get('imsi'):
sw = self.update_imsi(p['imsi']) sw = self.update_imsi(p['imsi'])
if sw != '9000': if sw != '9000':
print("Programming IMSI failed with code %s"%sw) print("Programming IMSI failed with code %s" % sw)
# EF.ACC # EF.ACC
if p.get('acc'): if p.get('acc'):
sw = self.update_acc(p['acc']) sw = self.update_acc(p['acc'])
if sw != '9000': if sw != '9000':
print("Programming ACC failed with code %s"%sw) print("Programming ACC failed with code %s" % sw)
# EF.PLMNsel # EF.PLMNsel
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_plmnsel(p['mcc'], p['mnc']) sw = self.update_plmnsel(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming PLMNsel failed with code %s"%sw) print("Programming PLMNsel failed with code %s" % sw)
# EF.PLMNwAcT # EF.PLMNwAcT
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_plmn_act(p['mcc'], p['mnc']) sw = self.update_plmn_act(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming PLMNwAcT failed with code %s"%sw) print("Programming PLMNwAcT failed with code %s" % sw)
# EF.OPLMNwAcT # EF.OPLMNwAcT
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_oplmn_act(p['mcc'], p['mnc']) sw = self.update_oplmn_act(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming OPLMNwAcT failed with code %s"%sw) print("Programming OPLMNwAcT failed with code %s" % sw)
# EF.AD # EF.AD
if (p.get('mcc') and p.get('mnc')) or p.get('opmode'): if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
@@ -1333,7 +1366,7 @@ class WavemobileSim(UsimCard):
mnc = None mnc = None
sw = self.update_ad(mnc=mnc, opmode=p.get('opmode')) sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'))
if sw != '9000': if sw != '9000':
print("Programming AD failed with code %s"%sw) print("Programming AD failed with code %s" % sw)
return None return None
@@ -1348,7 +1381,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
def __init__(self, ssc): def __init__(self, ssc):
super(SysmoISIMSJA2, self).__init__(ssc) super(SysmoISIMSJA2, self).__init__(ssc)
self._scc.cla_byte = "00" self._scc.cla_byte = "00"
self._scc.sel_ctrl = "0004" #request an FCP self._scc.sel_ctrl = "0004" # request an FCP
@classmethod @classmethod
def autodetect(kls, scc): def autodetect(kls, scc):
@@ -1374,7 +1407,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
def verify_adm(self, key): def verify_adm(self, key):
# authenticate as ADM using default key (written on the card..) # authenticate as ADM using default key (written on the card..)
if not key: if not key:
raise ValueError("Please provide a PIN-ADM as there is no default one") raise ValueError(
"Please provide a PIN-ADM as there is no default one")
(res, sw) = self._scc.verify_chv(0x0A, key) (res, sw) = self._scc.verify_chv(0x0A, key)
return sw return sw
@@ -1386,7 +1420,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
# license management, so the ICCID must be kept at its factory # license management, so the ICCID must be kept at its factory
# setting! # setting!
if p.get('iccid'): if p.get('iccid'):
print("Warning: Programming of the ICCID is not implemented for this type of card.") print(
"Warning: Programming of the ICCID is not implemented for this type of card.")
# select DF_GSM # select DF_GSM
self._scc.select_path(['7f20']) self._scc.select_path(['7f20'])
@@ -1403,25 +1438,25 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_plmnsel(p['mcc'], p['mnc']) sw = self.update_plmnsel(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming PLMNsel failed with code %s"%sw) print("Programming PLMNsel failed with code %s" % sw)
# EF.PLMNwAcT # EF.PLMNwAcT
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_plmn_act(p['mcc'], p['mnc']) sw = self.update_plmn_act(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming PLMNwAcT failed with code %s"%sw) print("Programming PLMNwAcT failed with code %s" % sw)
# EF.OPLMNwAcT # EF.OPLMNwAcT
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_oplmn_act(p['mcc'], p['mnc']) sw = self.update_oplmn_act(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming OPLMNwAcT failed with code %s"%sw) print("Programming OPLMNwAcT failed with code %s" % sw)
# EF.HPLMNwAcT # EF.HPLMNwAcT
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_hplmn_act(p['mcc'], p['mnc']) sw = self.update_hplmn_act(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming HPLMNwAcT failed with code %s"%sw) print("Programming HPLMNwAcT failed with code %s" % sw)
# EF.AD # EF.AD
if (p.get('mcc') and p.get('mnc')) or p.get('opmode'): if (p.get('mcc') and p.get('mnc')) or p.get('opmode'):
@@ -1431,12 +1466,13 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
mnc = None mnc = None
sw = self.update_ad(mnc=mnc, opmode=p.get('opmode')) sw = self.update_ad(mnc=mnc, opmode=p.get('opmode'))
if sw != '9000': if sw != '9000':
print("Programming AD failed with code %s"%sw) print("Programming AD failed with code %s" % sw)
# EF.SMSP # EF.SMSP
if p.get('smsp'): if p.get('smsp'):
r = self._scc.select_path(['3f00', '7f10']) r = self._scc.select_path(['3f00', '7f10'])
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True) data, sw = self._scc.update_record(
'6f42', 1, lpad(p['smsp'], 104), force_len=True)
# EF.MSISDN # EF.MSISDN
# TODO: Alpha Identifier (currently 'ff'O * 20) # TODO: Alpha Identifier (currently 'ff'O * 20)
@@ -1447,13 +1483,14 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
content = 'ff' * 20 + msisdn content = 'ff' * 20 + msisdn
r = self._scc.select_path(['3f00', '7f10']) r = self._scc.select_path(['3f00', '7f10'])
data, sw = self._scc.update_record('6F40', 1, content, force_len=True) data, sw = self._scc.update_record(
'6F40', 1, content, force_len=True)
# EF.ACC # EF.ACC
if p.get('acc'): if p.get('acc'):
sw = self.update_acc(p['acc']) sw = self.update_acc(p['acc'])
if sw != '9000': if sw != '9000':
print("Programming ACC failed with code %s"%sw) print("Programming ACC failed with code %s" % sw)
# Populate AIDs # Populate AIDs
self.read_aids() self.read_aids()
@@ -1482,8 +1519,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
else: else:
sw = self.update_pcscf("") sw = self.update_pcscf("")
if sw != '9000': if sw != '9000':
print("Programming P-CSCF failed with code %s"%sw) print("Programming P-CSCF failed with code %s" % sw)
# update EF.DOMAIN in ADF.ISIM # update EF.DOMAIN in ADF.ISIM
if self.file_exists(EF_ISIM_ADF_map['DOMAIN']): if self.file_exists(EF_ISIM_ADF_map['DOMAIN']):
@@ -1493,7 +1529,8 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
sw = self.update_domain() sw = self.update_domain()
if sw != '9000': if sw != '9000':
print("Programming Home Network Domain Name failed with code %s"%sw) print(
"Programming Home Network Domain Name failed with code %s" % sw)
# update EF.IMPI in ADF.ISIM # update EF.IMPI in ADF.ISIM
# TODO: Validate IMPI input # TODO: Validate IMPI input
@@ -1503,7 +1540,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
else: else:
sw = self.update_impi() sw = self.update_impi()
if sw != '9000': if sw != '9000':
print("Programming IMPI failed with code %s"%sw) print("Programming IMPI failed with code %s" % sw)
# update EF.IMPU in ADF.ISIM # update EF.IMPU in ADF.ISIM
# TODO: Validate IMPU input # TODO: Validate IMPU input
@@ -1514,7 +1551,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
else: else:
sw = self.update_impu() sw = self.update_impu()
if sw != '9000': if sw != '9000':
print("Programming IMPU failed with code %s"%sw) print("Programming IMPU failed with code %s" % sw)
data, sw = self.select_adf_by_aid(adf="usim") data, sw = self.select_adf_by_aid(adf="usim")
if sw == '9000': if sw == '9000':
@@ -1529,7 +1566,7 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
if p.get('mcc') and p.get('mnc'): if p.get('mcc') and p.get('mnc'):
sw = self.update_ehplmn(p['mcc'], p['mnc']) sw = self.update_ehplmn(p['mcc'], p['mnc'])
if sw != '9000': if sw != '9000':
print("Programming EHPLMN failed with code %s"%sw) print("Programming EHPLMN failed with code %s" % sw)
# update EF.ePDGId in ADF.USIM # update EF.ePDGId in ADF.USIM
if self.file_exists(EF_USIM_ADF_map['ePDGId']): if self.file_exists(EF_USIM_ADF_map['ePDGId']):
@@ -1538,18 +1575,18 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
else: else:
sw = self.update_epdgid("") sw = self.update_epdgid("")
if sw != '9000': if sw != '9000':
print("Programming ePDGId failed with code %s"%sw) print("Programming ePDGId failed with code %s" % sw)
# update EF.ePDGSelection in ADF.USIM # update EF.ePDGSelection in ADF.USIM
if self.file_exists(EF_USIM_ADF_map['ePDGSelection']): if self.file_exists(EF_USIM_ADF_map['ePDGSelection']):
if p.get('epdgSelection'): if p.get('epdgSelection'):
epdg_plmn = p['epdgSelection'] epdg_plmn = p['epdgSelection']
sw = self.update_ePDGSelection(epdg_plmn[:3], epdg_plmn[3:]) sw = self.update_ePDGSelection(
epdg_plmn[:3], epdg_plmn[3:])
else: else:
sw = self.update_ePDGSelection("", "") sw = self.update_ePDGSelection("", "")
if sw != '9000': if sw != '9000':
print("Programming ePDGSelection failed with code %s"%sw) print("Programming ePDGSelection failed with code %s" % sw)
# After successfully programming EF.ePDGId and EF.ePDGSelection, # After successfully programming EF.ePDGId and EF.ePDGSelection,
# Set service 106 and 107 as available in EF.UST # Set service 106 and 107 as available in EF.UST
@@ -1558,28 +1595,29 @@ class SysmoISIMSJA2(UsimCard, IsimCard):
if p.get('epdgSelection') and p.get('epdgid'): if p.get('epdgSelection') and p.get('epdgid'):
sw = self.update_ust(106, 1) sw = self.update_ust(106, 1)
if sw != '9000': if sw != '9000':
print("Programming UST failed with code %s"%sw) print("Programming UST failed with code %s" % sw)
sw = self.update_ust(107, 1) sw = self.update_ust(107, 1)
if sw != '9000': if sw != '9000':
print("Programming UST failed with code %s"%sw) print("Programming UST failed with code %s" % sw)
sw = self.update_ust(95, 0) sw = self.update_ust(95, 0)
if sw != '9000': if sw != '9000':
print("Programming UST failed with code %s"%sw) print("Programming UST failed with code %s" % sw)
sw = self.update_ust(99, 0) sw = self.update_ust(99, 0)
if sw != '9000': if sw != '9000':
print("Programming UST failed with code %s"%sw) print("Programming UST failed with code %s" % sw)
sw = self.update_ust(115, 0) sw = self.update_ust(115, 0)
if sw != '9000': if sw != '9000':
print("Programming UST failed with code %s"%sw) print("Programming UST failed with code %s" % sw)
return return
# In order for autodetection ... # In order for autodetection ...
_cards_classes = [ FakeMagicSim, SuperSim, MagicSim, GrcardSim, _cards_classes = [FakeMagicSim, SuperSim, MagicSim, GrcardSim,
SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1, SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1,
FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2 ] FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2]
def card_detect(ctype, scc): def card_detect(ctype, scc):
# Detect type if needed # Detect type if needed

View File

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

View File

@@ -26,6 +26,7 @@ from pySim.construct import LV
from pySim.utils import rpad, b2h, h2b, sw_match, bertlv_encode_len, Hexstr, h2i, str_sanitize from pySim.utils import rpad, b2h, h2b, sw_match, bertlv_encode_len, Hexstr, h2i, str_sanitize
from pySim.exceptions import SwMatchError from pySim.exceptions import SwMatchError
class SimCardCommands(object): class SimCardCommands(object):
def __init__(self, transport): def __init__(self, transport):
self._tp = transport self._tp = transport
@@ -37,13 +38,15 @@ class SimCardCommands(object):
# see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF, # see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF,
# DF or ADF # DF or ADF
from pytlv.TLV import TLV from pytlv.TLV import TLV
tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b', '8c', '80', 'ab', 'c6', '81', '88']) tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b',
'8c', '80', 'ab', 'c6', '81', '88'])
# pytlv is case sensitive! # pytlv is case sensitive!
fcp = fcp.lower() fcp = fcp.lower()
if fcp[0:2] != '62': if fcp[0:2] != '62':
raise ValueError('Tag of the FCP template does not match, expected 62 but got %s'%fcp[0:2]) raise ValueError(
'Tag of the FCP template does not match, expected 62 but got %s' % fcp[0:2])
# Unfortunately the spec is not very clear if the FCP length is # Unfortunately the spec is not very clear if the FCP length is
# coded as one or two byte vale, so we have to try it out by # coded as one or two byte vale, so we have to try it out by
@@ -100,7 +103,8 @@ class SimCardCommands(object):
if type(dir_list) is not list: if type(dir_list) is not list:
dir_list = [dir_list] dir_list = [dir_list]
for i in dir_list: for i in dir_list:
data, sw = self._tp.send_apdu(self.cla_byte + "a4" + self.sel_ctrl + "02" + i) data, sw = self._tp.send_apdu(
self.cla_byte + "a4" + self.sel_ctrl + "02" + i)
rv.append((data, sw)) rv.append((data, sw))
if sw != '9000': if sw != '9000':
return rv return rv
@@ -123,7 +127,7 @@ class SimCardCommands(object):
rv.append(data) rv.append(data)
return rv return rv
def select_file(self, fid:str): def select_file(self, fid: str):
"""Execute SELECT a given file by FID. """Execute SELECT a given file by FID.
Args: Args:
@@ -132,7 +136,7 @@ class SimCardCommands(object):
return self._tp.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + fid) return self._tp.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + fid)
def select_adf(self, aid:str): def select_adf(self, aid: str):
"""Execute SELECT a given Applicaiton ADF. """Execute SELECT a given Applicaiton ADF.
Args: Args:
@@ -142,7 +146,7 @@ class SimCardCommands(object):
aidlen = ("0" + format(len(aid) // 2, 'x'))[-2:] aidlen = ("0" + format(len(aid) // 2, 'x'))[-2:]
return self._tp.send_apdu_checksw(self.cla_byte + "a4" + "0404" + aidlen + aid) return self._tp.send_apdu_checksw(self.cla_byte + "a4" + "0404" + aidlen + aid)
def read_binary(self, ef, length:int=None, offset:int=0): def read_binary(self, ef, length: int = None, offset: int = 0):
"""Execute READD BINARY. """Execute READD BINARY.
Args: Args:
@@ -162,16 +166,18 @@ class SimCardCommands(object):
chunk_offset = 0 chunk_offset = 0
while chunk_offset < length: while chunk_offset < length:
chunk_len = min(255, length-chunk_offset) chunk_len = min(255, length-chunk_offset)
pdu = self.cla_byte + 'b0%04x%02x' % (offset + chunk_offset, chunk_len) pdu = self.cla_byte + \
'b0%04x%02x' % (offset + chunk_offset, chunk_len)
try: try:
data, sw = self._tp.send_apdu_checksw(pdu) data, sw = self._tp.send_apdu_checksw(pdu)
except Exception as e: except Exception as e:
raise ValueError('%s, failed to read (offset %d)' % (str_sanitize(str(e)), offset)) raise ValueError('%s, failed to read (offset %d)' %
(str_sanitize(str(e)), offset))
total_data += data total_data += data
chunk_offset += chunk_len chunk_offset += chunk_len
return total_data, sw return total_data, sw
def update_binary(self, ef, data:str, offset:int=0, verify:bool=False, conserve:bool=False): def update_binary(self, ef, data: str, offset: int = 0, verify: bool = False, conserve: bool = False):
"""Execute UPDATE BINARY. """Execute UPDATE BINARY.
Args: Args:
@@ -194,11 +200,13 @@ class SimCardCommands(object):
while chunk_offset < data_length: while chunk_offset < data_length:
chunk_len = min(255, data_length - chunk_offset) chunk_len = min(255, data_length - chunk_offset)
# chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2 # chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2
pdu = self.cla_byte + 'd6%04x%02x' % (offset + chunk_offset, chunk_len) + data[chunk_offset*2 : (chunk_offset+chunk_len)*2] pdu = self.cla_byte + \
'd6%04x%02x' % (offset + chunk_offset, chunk_len) + \
data[chunk_offset*2: (chunk_offset+chunk_len)*2]
try: try:
chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu) chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu)
except Exception as e: except Exception as e:
raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' % \ raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' %
(str_sanitize(str(e)), chunk_offset, chunk_len)) (str_sanitize(str(e)), chunk_offset, chunk_len))
total_data += data total_data += data
chunk_offset += chunk_len chunk_offset += chunk_len
@@ -206,7 +214,7 @@ class SimCardCommands(object):
self.verify_binary(ef, data, offset) self.verify_binary(ef, data, offset)
return total_data, chunk_sw return total_data, chunk_sw
def verify_binary(self, ef, data:str, offset:int=0): def verify_binary(self, ef, data: str, offset: int = 0):
"""Verify contents of transparent EF. """Verify contents of transparent EF.
Args: Args:
@@ -216,9 +224,10 @@ class SimCardCommands(object):
""" """
res = self.read_binary(ef, len(data) // 2, offset) res = self.read_binary(ef, len(data) // 2, offset)
if res[0].lower() != data.lower(): if res[0].lower() != data.lower():
raise ValueError('Binary verification failed (expected %s, got %s)' % (data.lower(), res[0].lower())) raise ValueError('Binary verification failed (expected %s, got %s)' % (
data.lower(), res[0].lower()))
def read_record(self, ef, rec_no:int): def read_record(self, ef, rec_no: int):
"""Execute READ RECORD. """Execute READ RECORD.
Args: Args:
@@ -230,8 +239,8 @@ class SimCardCommands(object):
pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length) pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
return self._tp.send_apdu_checksw(pdu) return self._tp.send_apdu_checksw(pdu)
def update_record(self, ef, rec_no:int, data:str, force_len:bool=False, verify:bool=False, def update_record(self, ef, rec_no: int, data: str, force_len: bool = False, verify: bool = False,
conserve:bool=False): conserve: bool = False):
"""Execute UPDATE RECORD. """Execute UPDATE RECORD.
Args: Args:
@@ -253,7 +262,8 @@ class SimCardCommands(object):
# exceed we throw an exception. # exceed we throw an exception.
rec_length = self.__record_len(res) rec_length = self.__record_len(res)
if (len(data) // 2 > rec_length): if (len(data) // 2 > rec_length):
raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (rec_length, len(data) // 2)) raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (
rec_length, len(data) // 2))
elif (len(data) // 2 < rec_length): elif (len(data) // 2 < rec_length):
data = rpad(data, rec_length * 2) data = rpad(data, rec_length * 2)
@@ -270,7 +280,7 @@ class SimCardCommands(object):
self.verify_record(ef, rec_no, data) self.verify_record(ef, rec_no, data)
return res return res
def verify_record(self, ef, rec_no:int, data:str): def verify_record(self, ef, rec_no: int, data: str):
"""Verify record against given data """Verify record against given data
Args: Args:
@@ -280,7 +290,8 @@ class SimCardCommands(object):
""" """
res = self.read_record(ef, rec_no) res = self.read_record(ef, rec_no)
if res[0].lower() != data.lower(): if res[0].lower() != data.lower():
raise ValueError('Record verification failed (expected %s, got %s)' % (data.lower(), res[0].lower())) raise ValueError('Record verification failed (expected %s, got %s)' % (
data.lower(), res[0].lower()))
def record_size(self, ef): def record_size(self, ef):
"""Determine the record size of given file. """Determine the record size of given file.
@@ -310,14 +321,14 @@ class SimCardCommands(object):
return self.__len(r) return self.__len(r)
# TS 102 221 Section 11.3.1 low-level helper # TS 102 221 Section 11.3.1 low-level helper
def _retrieve_data(self, tag:int, first:bool=True): def _retrieve_data(self, tag: int, first: bool = True):
if first: if first:
pdu = '80cb008001%02x' % (tag) pdu = '80cb008001%02x' % (tag)
else: else:
pdu = '80cb000000' pdu = '80cb000000'
return self._tp.send_apdu_checksw(pdu) return self._tp.send_apdu_checksw(pdu)
def retrieve_data(self, ef, tag:int): def retrieve_data(self, ef, tag: int):
"""Execute RETRIEVE DATA, see also TS 102 221 Section 11.3.1. """Execute RETRIEVE DATA, see also TS 102 221 Section 11.3.1.
Args Args
@@ -337,7 +348,7 @@ class SimCardCommands(object):
return total_data, sw return total_data, sw
# TS 102 221 Section 11.3.2 low-level helper # TS 102 221 Section 11.3.2 low-level helper
def _set_data(self, data:str, first:bool=True): def _set_data(self, data: str, first: bool = True):
if first: if first:
p1 = 0x80 p1 = 0x80
else: else:
@@ -347,7 +358,7 @@ class SimCardCommands(object):
pdu = '80db00%02x%02x%s' % (p1, len(data)//2, data) pdu = '80db00%02x%02x%s' % (p1, len(data)//2, data)
return self._tp.send_apdu_checksw(pdu) return self._tp.send_apdu_checksw(pdu)
def set_data(self, ef, tag:int, value:str, verify:bool=False, conserve:bool=False): def set_data(self, ef, tag: int, value: str, verify: bool = False, conserve: bool = False):
"""Execute SET DATA. """Execute SET DATA.
Args Args
@@ -378,7 +389,7 @@ class SimCardCommands(object):
remaining = remaining[255:] remaining = remaining[255:]
return rdata, sw return rdata, sw
def run_gsm(self, rand:str): def run_gsm(self, rand: str):
"""Execute RUN GSM ALGORITHM. """Execute RUN GSM ALGORITHM.
Args: Args:
@@ -389,7 +400,7 @@ class SimCardCommands(object):
self.select_path(['3f00', '7f20']) self.select_path(['3f00', '7f20'])
return self._tp.send_apdu(self.cla_byte + '88000010' + rand) return self._tp.send_apdu(self.cla_byte + '88000010' + rand)
def authenticate(self, rand:str, autn:str, context='3g'): def authenticate(self, rand: str, autn: str, context='3g'):
"""Execute AUTHENTICATE (USIM/ISIM). """Execute AUTHENTICATE (USIM/ISIM).
Args: Args:
@@ -400,7 +411,8 @@ class SimCardCommands(object):
# 3GPP TS 31.102 Section 7.1.2.1 # 3GPP TS 31.102 Section 7.1.2.1
AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV)) AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV))
AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV) AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV)
AuthResp3GSuccess = Struct(Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV)) AuthResp3GSuccess = Struct(
Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV))
AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess) AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess)
# build parameters # build parameters
cmd_data = {'rand': rand, 'autn': autn} cmd_data = {'rand': rand, 'autn': autn}
@@ -408,7 +420,8 @@ class SimCardCommands(object):
p2 = '81' p2 = '81'
elif context == 'gsm': elif context == 'gsm':
p2 = '80' p2 = '80'
(data, sw) = self._tp.send_apdu_constr_checksw(self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G) (data, sw) = self._tp.send_apdu_constr_checksw(
self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G)
if 'auts' in data: if 'auts' in data:
ret = {'synchronisation_failure': data} ret = {'synchronisation_failure': data}
else: else:
@@ -456,7 +469,7 @@ class SimCardCommands(object):
elif (sw != '9000'): elif (sw != '9000'):
raise SwMatchError(sw, '9000') raise SwMatchError(sw, '9000')
def verify_chv(self, chv_no:int, code:str): def verify_chv(self, chv_no: int, code: str):
"""Verify a given CHV (Card Holder Verification == PIN) """Verify a given CHV (Card Holder Verification == PIN)
Args: Args:
@@ -464,11 +477,12 @@ class SimCardCommands(object):
code : chv code as hex string code : chv code as hex string
""" """
fc = rpad(b2h(code), 16) fc = rpad(b2h(code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)
self._chv_process_sw('verify', chv_no, code, sw) self._chv_process_sw('verify', chv_no, code, sw)
return (data, sw) return (data, sw)
def unblock_chv(self, chv_no:int, puk_code:str, pin_code:str): def unblock_chv(self, chv_no: int, puk_code: str, pin_code: str):
"""Unblock a given CHV (Card Holder Verification == PIN) """Unblock a given CHV (Card Holder Verification == PIN)
Args: Args:
@@ -477,11 +491,12 @@ class SimCardCommands(object):
pin_code : new chv code as hex string pin_code : new chv code as hex string
""" """
fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16) fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc)
self._chv_process_sw('unblock', chv_no, pin_code, sw) self._chv_process_sw('unblock', chv_no, pin_code, sw)
return (data, sw) return (data, sw)
def change_chv(self, chv_no:int, pin_code:str, new_pin_code:str): def change_chv(self, chv_no: int, pin_code: str, new_pin_code: str):
"""Change a given CHV (Card Holder Verification == PIN) """Change a given CHV (Card Holder Verification == PIN)
Args: Args:
@@ -490,11 +505,12 @@ class SimCardCommands(object):
new_pin_code : new chv code as hex string new_pin_code : new chv code as hex string
""" """
fc = rpad(b2h(pin_code), 16) + rpad(b2h(new_pin_code), 16) fc = rpad(b2h(pin_code), 16) + rpad(b2h(new_pin_code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc)
self._chv_process_sw('change', chv_no, pin_code, sw) self._chv_process_sw('change', chv_no, pin_code, sw)
return (data, sw) return (data, sw)
def disable_chv(self, chv_no:int, pin_code:str): def disable_chv(self, chv_no: int, pin_code: str):
"""Disable a given CHV (Card Holder Verification == PIN) """Disable a given CHV (Card Holder Verification == PIN)
Args: Args:
@@ -503,11 +519,12 @@ class SimCardCommands(object):
new_pin_code : new chv code as hex string new_pin_code : new chv code as hex string
""" """
fc = rpad(b2h(pin_code), 16) fc = rpad(b2h(pin_code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc)
self._chv_process_sw('disable', chv_no, pin_code, sw) self._chv_process_sw('disable', chv_no, pin_code, sw)
return (data, sw) return (data, sw)
def enable_chv(self, chv_no:int, pin_code:str): def enable_chv(self, chv_no: int, pin_code: str):
"""Enable a given CHV (Card Holder Verification == PIN) """Enable a given CHV (Card Holder Verification == PIN)
Args: Args:
@@ -515,11 +532,12 @@ class SimCardCommands(object):
pin_code : chv code as hex string pin_code : chv code as hex string
""" """
fc = rpad(b2h(pin_code), 16) fc = rpad(b2h(pin_code), 16)
data, sw = self._tp.send_apdu(self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc) data, sw = self._tp.send_apdu(
self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc)
self._chv_process_sw('enable', chv_no, pin_code, sw) self._chv_process_sw('enable', chv_no, pin_code, sw)
return (data, sw) return (data, sw)
def envelope(self, payload:str): def envelope(self, payload: str):
"""Send one ENVELOPE command to the SIM """Send one ENVELOPE command to the SIM
Args: Args:
@@ -527,7 +545,7 @@ class SimCardCommands(object):
""" """
return self._tp.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload)) return self._tp.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload))
def terminal_profile(self, payload:str): def terminal_profile(self, payload: str):
"""Send TERMINAL PROFILE to card """Send TERMINAL PROFILE to card
Args: Args:
@@ -538,14 +556,14 @@ class SimCardCommands(object):
return (data, sw) return (data, sw)
# ETSI TS 102 221 11.1.22 # ETSI TS 102 221 11.1.22
def suspend_uicc(self, min_len_secs:int=60, max_len_secs:int=43200): def suspend_uicc(self, min_len_secs: int = 60, max_len_secs: int = 43200):
"""Send SUSPEND UICC to the card. """Send SUSPEND UICC to the card.
Args: Args:
min_len_secs : mimumum suspend time seconds min_len_secs : mimumum suspend time seconds
max_len_secs : maximum suspend time seconds max_len_secs : maximum suspend time seconds
""" """
def encode_duration(secs:int) -> Hexstr: def encode_duration(secs: int) -> Hexstr:
if secs >= 10*24*60*60: if secs >= 10*24*60*60:
return '04%02x' % (secs // (10*24*60*60)) return '04%02x' % (secs // (10*24*60*60))
elif secs >= 24*60*60: elif secs >= 24*60*60:
@@ -556,7 +574,8 @@ class SimCardCommands(object):
return '01%02x' % (secs // 60) return '01%02x' % (secs // 60)
else: else:
return '00%02x' % secs return '00%02x' % secs
def decode_duration(enc:Hexstr) -> int:
def decode_duration(enc: Hexstr) -> int:
time_unit = enc[:2] time_unit = enc[:2]
length = h2i(enc[2:4]) length = h2i(enc[2:4])
if time_unit == '04': if time_unit == '04':
@@ -573,7 +592,8 @@ class SimCardCommands(object):
raise ValueError('Time unit must be 0x00..0x04') raise ValueError('Time unit must be 0x00..0x04')
min_dur_enc = encode_duration(min_len_secs) min_dur_enc = encode_duration(min_len_secs)
max_dur_enc = encode_duration(max_len_secs) max_dur_enc = encode_duration(max_len_secs)
data, sw = self._tp.send_apdu_checksw('8076000004' + min_dur_enc + max_dur_enc) data, sw = self._tp.send_apdu_checksw(
'8076000004' + min_dur_enc + max_dur_enc)
negotiated_duration_secs = decode_duration(data[:4]) negotiated_duration_secs = decode_duration(data[:4])
resume_token = data[4:] resume_token = data[4:]
return (negotiated_duration_secs, resume_token, sw) return (negotiated_duration_secs, resume_token, sw)

View File

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

View File

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

View File

@@ -44,6 +44,7 @@ from pySim.exceptions import *
from pySim.jsonpath import js_path_find, js_path_modify from pySim.jsonpath import js_path_find, js_path_modify
from pySim.commands import SimCardCommands from pySim.commands import SimCardCommands
class CardFile(object): class CardFile(object):
"""Base class for all objects in the smart card filesystem. """Base class for all objects in the smart card filesystem.
Serve as a common ancestor to all other file types; rarely used directly. Serve as a common ancestor to all other file types; rarely used directly.
@@ -51,8 +52,8 @@ class CardFile(object):
RESERVED_NAMES = ['..', '.', '/', 'MF'] RESERVED_NAMES = ['..', '.', '/', 'MF']
RESERVED_FIDS = ['3f00'] RESERVED_FIDS = ['3f00']
def __init__(self, fid:str=None, sfid:str=None, name:str=None, desc:str=None, def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None,
parent:Optional['CardDF']=None, profile:Optional['CardProfile']=None): parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None):
""" """
Args: Args:
fid : File Identifier (4 hex digits) fid : File Identifier (4 hex digits)
@@ -86,13 +87,13 @@ class CardFile(object):
else: else:
return self.fid return self.fid
def _path_element(self, prefer_name:bool) -> Optional[str]: def _path_element(self, prefer_name: bool) -> Optional[str]:
if prefer_name and self.name: if prefer_name and self.name:
return self.name return self.name
else: else:
return self.fid return self.fid
def fully_qualified_path(self, prefer_name:bool=True) -> List[str]: def fully_qualified_path(self, prefer_name: bool = True) -> List[str]:
"""Return fully qualified path to file as list of FID or name strings. """Return fully qualified path to file as list of FID or name strings.
Args: Args:
@@ -117,7 +118,7 @@ class CardFile(object):
node = node.parent node = node.parent
return cast(CardMF, node) return cast(CardMF, node)
def _get_self_selectables(self, alias:str=None, flags = []) -> Dict[str, 'CardFile']: def _get_self_selectables(self, alias: str = None, flags=[]) -> Dict[str, 'CardFile']:
"""Return a dict of {'identifier': self} tuples. """Return a dict of {'identifier': self} tuples.
Args: Args:
@@ -136,7 +137,7 @@ class CardFile(object):
sels.update({self.name: self}) sels.update({self.name: self})
return sels return sels
def get_selectables(self, flags = []) -> Dict[str, 'CardFile']: def get_selectables(self, flags=[]) -> Dict[str, 'CardFile']:
"""Return a dict of {'identifier': File} that is selectable from the current file. """Return a dict of {'identifier': File} that is selectable from the current file.
Args: Args:
@@ -158,11 +159,11 @@ class CardFile(object):
if flags == [] or 'MF' in flags: if flags == [] or 'MF' in flags:
mf = self.get_mf() mf = self.get_mf()
if mf: if mf:
sels.update(mf._get_self_selectables(flags = flags)) sels.update(mf._get_self_selectables(flags=flags))
sels.update(mf.get_app_selectables(flags = flags)) sels.update(mf.get_app_selectables(flags=flags))
return sels return sels
def get_selectable_names(self, flags = []) -> List[str]: def get_selectable_names(self, flags=[]) -> List[str]:
"""Return a dict of {'identifier': File} that is selectable from the current file. """Return a dict of {'identifier': File} that is selectable from the current file.
Args: Args:
@@ -176,7 +177,7 @@ class CardFile(object):
sel_keys.sort() sel_keys.sort()
return sel_keys return sel_keys
def decode_select_response(self, data_hex:str): def decode_select_response(self, data_hex: str):
"""Decode the response to a SELECT command. """Decode the response to a SELECT command.
Args: Args:
@@ -206,6 +207,7 @@ class CardFile(object):
return self.parent.get_profile() return self.parent.get_profile()
return None return None
class CardDF(CardFile): class CardDF(CardFile):
"""DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories.""" """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories."""
@@ -225,7 +227,7 @@ class CardDF(CardFile):
def __str__(self): def __str__(self):
return "DF(%s)" % (super().__str__()) return "DF(%s)" % (super().__str__())
def add_file(self, child:CardFile, ignore_existing:bool=False): def add_file(self, child: CardFile, ignore_existing: bool = False):
"""Add a child (DF/EF) to this DF. """Add a child (DF/EF) to this DF.
Args: Args:
child: The new DF/EF to be added child: The new DF/EF to be added
@@ -233,7 +235,7 @@ class CardDF(CardFile):
""" """
if not isinstance(child, CardFile): if not isinstance(child, CardFile):
raise TypeError("Expected a File instance") raise TypeError("Expected a File instance")
if not is_hex(child.fid, minlen = 4, maxlen = 4): if not is_hex(child.fid, minlen=4, maxlen=4):
raise ValueError("File name %s is not a valid fid" % (child.fid)) raise ValueError("File name %s is not a valid fid" % (child.fid))
if child.name in CardFile.RESERVED_NAMES: if child.name in CardFile.RESERVED_NAMES:
raise ValueError("File name %s is a reserved name" % (child.name)) raise ValueError("File name %s is a reserved name" % (child.name))
@@ -242,17 +244,20 @@ class CardDF(CardFile):
if child.fid in self.children: if child.fid in self.children:
if ignore_existing: if ignore_existing:
return return
raise ValueError("File with given fid %s already exists in %s" % (child.fid, self)) raise ValueError(
"File with given fid %s already exists in %s" % (child.fid, self))
if self.lookup_file_by_sfid(child.sfid): if self.lookup_file_by_sfid(child.sfid):
raise ValueError("File with given sfid %s already exists in %s" % (child.sfid, self)) raise ValueError(
"File with given sfid %s already exists in %s" % (child.sfid, self))
if self.lookup_file_by_name(child.name): if self.lookup_file_by_name(child.name):
if ignore_existing: if ignore_existing:
return return
raise ValueError("File with given name %s already exists in %s" % (child.name, self)) raise ValueError(
"File with given name %s already exists in %s" % (child.name, self))
self.children[child.fid] = child self.children[child.fid] = child
child.parent = self child.parent = self
def add_files(self, children:Iterable[CardFile], ignore_existing:bool=False): def add_files(self, children: Iterable[CardFile], ignore_existing: bool = False):
"""Add a list of child (DF/EF) to this DF """Add a list of child (DF/EF) to this DF
Args: Args:
@@ -262,7 +267,7 @@ class CardDF(CardFile):
for child in children: for child in children:
self.add_file(child, ignore_existing) self.add_file(child, ignore_existing)
def get_selectables(self, flags = []) -> dict: def get_selectables(self, flags=[]) -> dict:
"""Return a dict of {'identifier': File} that is selectable from the current DF. """Return a dict of {'identifier': File} that is selectable from the current DF.
Args: Args:
@@ -280,7 +285,7 @@ class CardDF(CardFile):
sels.update({x.name: x for x in self.children.values() if x.name}) sels.update({x.name: x for x in self.children.values() if x.name})
return sels return sels
def lookup_file_by_name(self, name:Optional[str]) -> Optional[CardFile]: def lookup_file_by_name(self, name: Optional[str]) -> Optional[CardFile]:
"""Find a file with given name within current DF.""" """Find a file with given name within current DF."""
if name == None: if name == None:
return None return None
@@ -289,7 +294,7 @@ class CardDF(CardFile):
return i return i
return None return None
def lookup_file_by_sfid(self, sfid:Optional[str]) -> Optional[CardFile]: def lookup_file_by_sfid(self, sfid: Optional[str]) -> Optional[CardFile]:
"""Find a file with given short file ID within current DF.""" """Find a file with given short file ID within current DF."""
if sfid == None: if sfid == None:
return None return None
@@ -298,7 +303,7 @@ class CardDF(CardFile):
return i return i
return None return None
def lookup_file_by_fid(self, fid:str) -> Optional[CardFile]: def lookup_file_by_fid(self, fid: str) -> Optional[CardFile]:
"""Find a file with given file ID within current DF.""" """Find a file with given file ID within current DF."""
if fid in self.children: if fid in self.children:
return self.children[fid] return self.children[fid]
@@ -307,6 +312,7 @@ class CardDF(CardFile):
class CardMF(CardDF): class CardMF(CardDF):
"""MF (Master File) in the smart card filesystem""" """MF (Master File) in the smart card filesystem"""
def __init__(self, **kwargs): def __init__(self, **kwargs):
# can be overridden; use setdefault # can be overridden; use setdefault
kwargs.setdefault('fid', '3f00') kwargs.setdefault('fid', '3f00')
@@ -320,20 +326,20 @@ class CardMF(CardDF):
def __str__(self): def __str__(self):
return "MF(%s)" % (self.fid) return "MF(%s)" % (self.fid)
def add_application_df(self, app:'CardADF'): def add_application_df(self, app: 'CardADF'):
"""Add an Application to the MF""" """Add an Application to the MF"""
if not isinstance(app, CardADF): if not isinstance(app, CardADF):
raise TypeError("Expected an ADF instance") raise TypeError("Expected an ADF instance")
if app.aid in self.applications: if app.aid in self.applications:
raise ValueError("AID %s already exists" % (app.aid)) raise ValueError("AID %s already exists" % (app.aid))
self.applications[app.aid] = app self.applications[app.aid] = app
app.parent=self app.parent = self
def get_app_names(self): def get_app_names(self):
"""Get list of completions (AID names)""" """Get list of completions (AID names)"""
return [x.name for x in self.applications] return [x.name for x in self.applications]
def get_selectables(self, flags = []) -> dict: def get_selectables(self, flags=[]) -> dict:
"""Return a dict of {'identifier': File} that is selectable from the current DF. """Return a dict of {'identifier': File} that is selectable from the current DF.
Args: Args:
@@ -347,16 +353,17 @@ class CardMF(CardDF):
sels.update(self.get_app_selectables(flags)) sels.update(self.get_app_selectables(flags))
return sels return sels
def get_app_selectables(self, flags = []) -> dict: def get_app_selectables(self, flags=[]) -> dict:
"""Get applications by AID + name""" """Get applications by AID + name"""
sels = {} sels = {}
if flags == [] or 'AIDS' in flags: if flags == [] or 'AIDS' in flags:
sels.update({x.aid: x for x in self.applications.values()}) sels.update({x.aid: x for x in self.applications.values()})
if flags == [] or 'ANAMES' in flags: if flags == [] or 'ANAMES' in flags:
sels.update({x.name: x for x in self.applications.values() if x.name}) sels.update(
{x.name: x for x in self.applications.values() if x.name})
return sels return sels
def decode_select_response(self, data_hex:str) -> object: def decode_select_response(self, data_hex: str) -> object:
"""Decode the response to a SELECT command. """Decode the response to a SELECT command.
This is the fall-back method which automatically defers to the standard decoding This is the fall-back method which automatically defers to the standard decoding
@@ -372,9 +379,11 @@ class CardMF(CardDF):
else: else:
return data_hex return data_hex
class CardADF(CardDF): class CardADF(CardDF):
"""ADF (Application Dedicated File) in the smart card filesystem""" """ADF (Application Dedicated File) in the smart card filesystem"""
def __init__(self, aid:str, **kwargs):
def __init__(self, aid: str, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
# reference to CardApplication may be set from CardApplication constructor # reference to CardApplication may be set from CardApplication constructor
self.application = None # type: Optional[CardApplication] self.application = None # type: Optional[CardApplication]
@@ -386,7 +395,7 @@ class CardADF(CardDF):
def __str__(self): def __str__(self):
return "ADF(%s)" % (self.aid) return "ADF(%s)" % (self.aid)
def _path_element(self, prefer_name:bool): def _path_element(self, prefer_name: bool):
if self.name and prefer_name: if self.name and prefer_name:
return self.name return self.name
else: else:
@@ -395,6 +404,7 @@ class CardADF(CardDF):
class CardEF(CardFile): class CardEF(CardFile):
"""EF (Entry File) in the smart card filesystem""" """EF (Entry File) in the smart card filesystem"""
def __init__(self, *, fid, **kwargs): def __init__(self, *, fid, **kwargs):
kwargs['fid'] = fid kwargs['fid'] = fid
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -402,7 +412,7 @@ class CardEF(CardFile):
def __str__(self): def __str__(self):
return "EF(%s)" % (super().__str__()) return "EF(%s)" % (super().__str__())
def get_selectables(self, flags = []) -> dict: def get_selectables(self, flags=[]) -> dict:
"""Return a dict of {'identifier': File} that is selectable from the current DF. """Return a dict of {'identifier': File} that is selectable from the current DF.
Args: Args:
@@ -412,9 +422,10 @@ class CardEF(CardFile):
dict containing all selectable items. Key is identifier (string), value dict containing all selectable items. Key is identifier (string), value
a reference to a CardFile (or derived class) instance. a reference to a CardFile (or derived class) instance.
""" """
#global selectable names + those of the parent DF # global selectable names + those of the parent DF
sels = super().get_selectables(flags) sels = super().get_selectables(flags)
sels.update({x.name:x for x in self.parent.children.values() if x != self}) sels.update(
{x.name: x for x in self.parent.children.values() if x != self})
return sels return sels
@@ -427,12 +438,16 @@ class TransparentEF(CardEF):
@with_default_category('Transparent EF Commands') @with_default_category('Transparent EF Commands')
class ShellCommands(CommandSet): class ShellCommands(CommandSet):
"""Shell commands specific for transparent EFs.""" """Shell commands specific for transparent EFs."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
read_bin_parser = argparse.ArgumentParser() read_bin_parser = argparse.ArgumentParser()
read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read') read_bin_parser.add_argument(
read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read') '--offset', type=int, default=0, help='Byte offset for start of read')
read_bin_parser.add_argument(
'--length', type=int, help='Number of bytes to read')
@cmd2.with_argparser(read_bin_parser) @cmd2.with_argparser(read_bin_parser)
def do_read_binary(self, opts): def do_read_binary(self, opts):
"""Read binary data from a transparent EF""" """Read binary data from a transparent EF"""
@@ -442,6 +457,7 @@ class TransparentEF(CardEF):
read_bin_dec_parser = argparse.ArgumentParser() read_bin_dec_parser = argparse.ArgumentParser()
read_bin_dec_parser.add_argument('--oneline', action='store_true', read_bin_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line') help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_bin_dec_parser) @cmd2.with_argparser(read_bin_dec_parser)
def do_read_binary_decoded(self, opts): def do_read_binary_decoded(self, opts):
"""Read + decode data from a transparent EF""" """Read + decode data from a transparent EF"""
@@ -449,8 +465,11 @@ class TransparentEF(CardEF):
self._cmd.poutput_json(data, opts.oneline) self._cmd.poutput_json(data, opts.oneline)
upd_bin_parser = argparse.ArgumentParser() upd_bin_parser = argparse.ArgumentParser()
upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read') upd_bin_parser.add_argument(
upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write') '--offset', type=int, default=0, help='Byte offset for start of read')
upd_bin_parser.add_argument(
'data', help='Data bytes (hex format) to write')
@cmd2.with_argparser(upd_bin_parser) @cmd2.with_argparser(upd_bin_parser)
def do_update_binary(self, opts): def do_update_binary(self, opts):
"""Update (Write) data of a transparent EF""" """Update (Write) data of a transparent EF"""
@@ -459,15 +478,18 @@ class TransparentEF(CardEF):
self._cmd.poutput(data) self._cmd.poutput(data)
upd_bin_dec_parser = argparse.ArgumentParser() upd_bin_dec_parser = argparse.ArgumentParser()
upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write') upd_bin_dec_parser.add_argument(
'data', help='Abstract data (JSON format) to write')
upd_bin_dec_parser.add_argument('--json-path', type=str, upd_bin_dec_parser.add_argument('--json-path', type=str,
help='JSON path to modify specific element of file only') help='JSON path to modify specific element of file only')
@cmd2.with_argparser(upd_bin_dec_parser) @cmd2.with_argparser(upd_bin_dec_parser)
def do_update_binary_decoded(self, opts): def do_update_binary_decoded(self, opts):
"""Encode + Update (Write) data of a transparent EF""" """Encode + Update (Write) data of a transparent EF"""
if opts.json_path: if opts.json_path:
(data_json, sw) = self._cmd.rs.read_binary_dec() (data_json, sw) = self._cmd.rs.read_binary_dec()
js_path_modify(data_json, opts.json_path, json.loads(opts.data)) js_path_modify(data_json, opts.json_path,
json.loads(opts.data))
else: else:
data_json = json.loads(opts.data) data_json = json.loads(opts.data)
(data, sw) = self._cmd.rs.update_binary_dec(data_json) (data, sw) = self._cmd.rs.update_binary_dec(data_json)
@@ -493,9 +515,8 @@ class TransparentEF(CardEF):
if data: if data:
self._cmd.poutput_json(data) self._cmd.poutput_json(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None, size={1, None}):
size={1,None}):
""" """
Args: Args:
fid : File Identifier (4 hex digits) fid : File Identifier (4 hex digits)
@@ -511,7 +532,7 @@ class TransparentEF(CardEF):
self.size = size self.size = size
self.shell_commands = [self.ShellCommands()] self.shell_commands = [self.ShellCommands()]
def decode_bin(self, raw_bin_data:bytearray) -> dict: def decode_bin(self, raw_bin_data: bytearray) -> dict:
"""Decode raw (binary) data into abstract representation. """Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_bin() or _decode_hex() method A derived class would typically provide a _decode_bin() or _decode_hex() method
@@ -537,7 +558,7 @@ class TransparentEF(CardEF):
return t.to_dict() return t.to_dict()
return {'raw': raw_bin_data.hex()} return {'raw': raw_bin_data.hex()}
def decode_hex(self, raw_hex_data:str) -> dict: def decode_hex(self, raw_hex_data: str) -> dict:
"""Decode raw (hex string) data into abstract representation. """Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_bin() or _decode_hex() method A derived class would typically provide a _decode_bin() or _decode_hex() method
@@ -564,7 +585,7 @@ class TransparentEF(CardEF):
return t.to_dict() return t.to_dict()
return {'raw': raw_bin_data.hex()} return {'raw': raw_bin_data.hex()}
def encode_bin(self, abstract_data:dict) -> bytearray: def encode_bin(self, abstract_data: dict) -> bytearray:
"""Encode abstract representation into raw (binary) data. """Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_bin() or _encode_hex() method A derived class would typically provide an _encode_bin() or _encode_hex() method
@@ -588,9 +609,10 @@ class TransparentEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() return t.to_tlv()
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def encode_hex(self, abstract_data:dict) -> str: def encode_hex(self, abstract_data: dict) -> str:
"""Encode abstract representation into raw (hex string) data. """Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_bin() or _encode_hex() method A derived class would typically provide an _encode_bin() or _encode_hex() method
@@ -615,7 +637,8 @@ class TransparentEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) return b2h(t.to_tlv())
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
class LinFixedEF(CardEF): class LinFixedEF(CardEF):
@@ -627,12 +650,16 @@ class LinFixedEF(CardEF):
@with_default_category('Linear Fixed EF Commands') @with_default_category('Linear Fixed EF Commands')
class ShellCommands(CommandSet): class ShellCommands(CommandSet):
"""Shell commands specific for Linear Fixed EFs.""" """Shell commands specific for Linear Fixed EFs."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
read_rec_parser = argparse.ArgumentParser() read_rec_parser = argparse.ArgumentParser()
read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read') read_rec_parser.add_argument(
read_rec_parser.add_argument('--count', type=int, default=1, help='Number of records to be read, beginning at record_nr') 'record_nr', type=int, help='Number of record to be read')
read_rec_parser.add_argument(
'--count', type=int, default=1, help='Number of records to be read, beginning at record_nr')
@cmd2.with_argparser(read_rec_parser) @cmd2.with_argparser(read_rec_parser)
def do_read_record(self, opts): def do_read_record(self, opts):
"""Read one or multiple records from a record-oriented EF""" """Read one or multiple records from a record-oriented EF"""
@@ -646,9 +673,11 @@ class LinFixedEF(CardEF):
self._cmd.poutput("%03d %s" % (recnr, recstr)) self._cmd.poutput("%03d %s" % (recnr, recstr))
read_rec_dec_parser = argparse.ArgumentParser() read_rec_dec_parser = argparse.ArgumentParser()
read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read') read_rec_dec_parser.add_argument(
'record_nr', type=int, help='Number of record to be read')
read_rec_dec_parser.add_argument('--oneline', action='store_true', read_rec_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line') help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_rec_dec_parser) @cmd2.with_argparser(read_rec_dec_parser)
def do_read_record_decoded(self, opts): def do_read_record_decoded(self, opts):
"""Read + decode a record from a record-oriented EF""" """Read + decode a record from a record-oriented EF"""
@@ -656,6 +685,7 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data, opts.oneline) self._cmd.poutput_json(data, opts.oneline)
read_recs_parser = argparse.ArgumentParser() read_recs_parser = argparse.ArgumentParser()
@cmd2.with_argparser(read_recs_parser) @cmd2.with_argparser(read_recs_parser)
def do_read_records(self, opts): def do_read_records(self, opts):
"""Read all records from a record-oriented EF""" """Read all records from a record-oriented EF"""
@@ -671,6 +701,7 @@ class LinFixedEF(CardEF):
read_recs_dec_parser = argparse.ArgumentParser() read_recs_dec_parser = argparse.ArgumentParser()
read_recs_dec_parser.add_argument('--oneline', action='store_true', read_recs_dec_parser.add_argument('--oneline', action='store_true',
help='No JSON pretty-printing, dump as a single line') help='No JSON pretty-printing, dump as a single line')
@cmd2.with_argparser(read_recs_dec_parser) @cmd2.with_argparser(read_recs_dec_parser)
def do_read_records_decoded(self, opts): def do_read_records_decoded(self, opts):
"""Read + decode all records from a record-oriented EF""" """Read + decode all records from a record-oriented EF"""
@@ -683,8 +714,11 @@ class LinFixedEF(CardEF):
self._cmd.poutput_json(data_list, opts.oneline) self._cmd.poutput_json(data_list, opts.oneline)
upd_rec_parser = argparse.ArgumentParser() upd_rec_parser = argparse.ArgumentParser()
upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read') upd_rec_parser.add_argument(
upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write') 'record_nr', type=int, help='Number of record to be read')
upd_rec_parser.add_argument(
'data', help='Data bytes (hex format) to write')
@cmd2.with_argparser(upd_rec_parser) @cmd2.with_argparser(upd_rec_parser)
def do_update_record(self, opts): def do_update_record(self, opts):
"""Update (write) data to a record-oriented EF""" """Update (write) data to a record-oriented EF"""
@@ -693,24 +727,31 @@ class LinFixedEF(CardEF):
self._cmd.poutput(data) self._cmd.poutput(data)
upd_rec_dec_parser = argparse.ArgumentParser() upd_rec_dec_parser = argparse.ArgumentParser()
upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read') upd_rec_dec_parser.add_argument(
upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write') 'record_nr', type=int, help='Number of record to be read')
upd_rec_dec_parser.add_argument(
'data', help='Abstract data (JSON format) to write')
upd_rec_dec_parser.add_argument('--json-path', type=str, upd_rec_dec_parser.add_argument('--json-path', type=str,
help='JSON path to modify specific element of record only') help='JSON path to modify specific element of record only')
@cmd2.with_argparser(upd_rec_dec_parser) @cmd2.with_argparser(upd_rec_dec_parser)
def do_update_record_decoded(self, opts): def do_update_record_decoded(self, opts):
"""Encode + Update (write) data to a record-oriented EF""" """Encode + Update (write) data to a record-oriented EF"""
if opts.json_path: if opts.json_path:
(data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr) (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr)
js_path_modify(data_json, opts.json_path, json.loads(opts.data)) js_path_modify(data_json, opts.json_path,
json.loads(opts.data))
else: else:
data_json = json.loads(opts.data) data_json = json.loads(opts.data)
(data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json) (data, sw) = self._cmd.rs.update_record_dec(
opts.record_nr, data_json)
if data: if data:
self._cmd.poutput(data) self._cmd.poutput(data)
edit_rec_dec_parser = argparse.ArgumentParser() edit_rec_dec_parser = argparse.ArgumentParser()
edit_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be edited') edit_rec_dec_parser.add_argument(
'record_nr', type=int, help='Number of record to be edited')
@cmd2.with_argparser(edit_rec_dec_parser) @cmd2.with_argparser(edit_rec_dec_parser)
def do_edit_record_decoded(self, opts): def do_edit_record_decoded(self, opts):
"""Edit the JSON representation of one record in an editor.""" """Edit the JSON representation of one record in an editor."""
@@ -727,13 +768,13 @@ class LinFixedEF(CardEF):
if edited_json == orig_json: if edited_json == orig_json:
self._cmd.poutput("Data not modified, skipping write") self._cmd.poutput("Data not modified, skipping write")
else: else:
(data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, edited_json) (data, sw) = self._cmd.rs.update_record_dec(
opts.record_nr, edited_json)
if data: if data:
self._cmd.poutput_json(data) self._cmd.poutput_json(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None,
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent: Optional[CardDF] = None, rec_len={1, None}):
parent:Optional[CardDF]=None, rec_len={1,None}):
""" """
Args: Args:
fid : File Identifier (4 hex digits) fid : File Identifier (4 hex digits)
@@ -749,7 +790,7 @@ class LinFixedEF(CardEF):
self._construct = None self._construct = None
self._tlv = None self._tlv = None
def decode_record_hex(self, raw_hex_data:str) -> dict: def decode_record_hex(self, raw_hex_data: str) -> dict:
"""Decode raw (hex string) data into abstract representation. """Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
@@ -776,7 +817,7 @@ class LinFixedEF(CardEF):
return t.to_dict() return t.to_dict()
return {'raw': raw_bin_data.hex()} return {'raw': raw_bin_data.hex()}
def decode_record_bin(self, raw_bin_data:bytearray) -> dict: def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
"""Decode raw (binary) data into abstract representation. """Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
@@ -803,7 +844,7 @@ class LinFixedEF(CardEF):
return t.to_dict() return t.to_dict()
return {'raw': raw_hex_data} return {'raw': raw_hex_data}
def encode_record_hex(self, abstract_data:dict) -> str: def encode_record_hex(self, abstract_data: dict) -> str:
"""Encode abstract representation into raw (hex string) data. """Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
@@ -828,9 +869,10 @@ class LinFixedEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) return b2h(t.to_tlv())
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def encode_record_bin(self, abstract_data:dict) -> bytearray: def encode_record_bin(self, abstract_data: dict) -> bytearray:
"""Encode abstract representation into raw (binary) data. """Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
@@ -854,14 +896,19 @@ class LinFixedEF(CardEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() return t.to_tlv()
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
class CyclicEF(LinFixedEF): class CyclicEF(LinFixedEF):
"""Cyclic EF (Entry File) in the smart card filesystem""" """Cyclic EF (Entry File) in the smart card filesystem"""
# we don't really have any special support for those; just recycling LinFixedEF here # we don't really have any special support for those; just recycling LinFixedEF here
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None,
rec_len={1,None}): def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len) rec_len={1, None}):
super().__init__(fid=fid, sfid=sfid, name=name,
desc=desc, parent=parent, rec_len=rec_len)
class TransRecEF(TransparentEF): class TransRecEF(TransparentEF):
"""Transparent EF (Entry File) containing fixed-size records. """Transparent EF (Entry File) containing fixed-size records.
@@ -872,8 +919,9 @@ class TransRecEF(TransparentEF):
We add a special class for those, so the user only has to provide encoder/decoder functions We add a special class for those, so the user only has to provide encoder/decoder functions
for a record, while this class takes care of split / merge of records. for a record, while this class takes care of split / merge of records.
""" """
def __init__(self, fid:str, rec_len:int, sfid:str=None, name:str=None, desc:str=None,
parent:Optional[CardDF]=None, size={1,None}): def __init__(self, fid: str, rec_len: int, sfid: str = None, name: str = None, desc: str = None,
parent: Optional[CardDF] = None, size={1, None}):
""" """
Args: Args:
fid : File Identifier (4 hex digits) fid : File Identifier (4 hex digits)
@@ -887,7 +935,7 @@ class TransRecEF(TransparentEF):
super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size) super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, size=size)
self.rec_len = rec_len self.rec_len = rec_len
def decode_record_hex(self, raw_hex_data:str) -> dict: def decode_record_hex(self, raw_hex_data: str) -> dict:
"""Decode raw (hex string) data into abstract representation. """Decode raw (hex string) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
@@ -914,7 +962,7 @@ class TransRecEF(TransparentEF):
return t.to_dict() return t.to_dict()
return {'raw': raw_hex_data} return {'raw': raw_hex_data}
def decode_record_bin(self, raw_bin_data:bytearray) -> dict: def decode_record_bin(self, raw_bin_data: bytearray) -> dict:
"""Decode raw (binary) data into abstract representation. """Decode raw (binary) data into abstract representation.
A derived class would typically provide a _decode_record_bin() or _decode_record_hex() A derived class would typically provide a _decode_record_bin() or _decode_record_hex()
@@ -941,7 +989,7 @@ class TransRecEF(TransparentEF):
return t.to_dict() return t.to_dict()
return {'raw': raw_hex_data} return {'raw': raw_hex_data}
def encode_record_hex(self, abstract_data:dict) -> str: def encode_record_hex(self, abstract_data: dict) -> str:
"""Encode abstract representation into raw (hex string) data. """Encode abstract representation into raw (hex string) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
@@ -965,9 +1013,10 @@ class TransRecEF(TransparentEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return b2h(t.to_tlv()) return b2h(t.to_tlv())
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def encode_record_bin(self, abstract_data:dict) -> bytearray: def encode_record_bin(self, abstract_data: dict) -> bytearray:
"""Encode abstract representation into raw (binary) data. """Encode abstract representation into raw (binary) data.
A derived class would typically provide an _encode_record_bin() or _encode_record_hex() A derived class would typically provide an _encode_record_bin() or _encode_record_hex()
@@ -991,10 +1040,12 @@ class TransRecEF(TransparentEF):
t = self._tlv() if inspect.isclass(self._tlv) else self._tlv t = self._tlv() if inspect.isclass(self._tlv) else self._tlv
t.from_dict(abstract_data) t.from_dict(abstract_data)
return t.to_tlv() return t.to_tlv()
raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) raise NotImplementedError(
"%s encoder not yet implemented. Patches welcome." % self)
def _decode_bin(self, raw_bin_data:bytearray): def _decode_bin(self, raw_bin_data: bytearray):
chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)] chunks = [raw_bin_data[i:i+self.rec_len]
for i in range(0, len(raw_bin_data), self.rec_len)]
return [self.decode_record_bin(x) for x in chunks] return [self.decode_record_bin(x) for x in chunks]
def _encode_bin(self, abstract_data) -> bytes: def _encode_bin(self, abstract_data) -> bytes:
@@ -1014,11 +1065,14 @@ class BerTlvEF(CardEF):
@with_default_category('BER-TLV EF Commands') @with_default_category('BER-TLV EF Commands')
class ShellCommands(CommandSet): class ShellCommands(CommandSet):
"""Shell commands specific for BER-TLV EFs.""" """Shell commands specific for BER-TLV EFs."""
def __init__(self): def __init__(self):
super().__init__() super().__init__()
retrieve_data_parser = argparse.ArgumentParser() retrieve_data_parser = argparse.ArgumentParser()
retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve') retrieve_data_parser.add_argument(
'tag', type=auto_int, help='BER-TLV Tag of value to retrieve')
@cmd2.with_argparser(retrieve_data_parser) @cmd2.with_argparser(retrieve_data_parser)
def do_retrieve_data(self, opts): def do_retrieve_data(self, opts):
"""Retrieve (Read) data from a BER-TLV EF""" """Retrieve (Read) data from a BER-TLV EF"""
@@ -1031,8 +1085,11 @@ class BerTlvEF(CardEF):
self._cmd.poutput(tags) self._cmd.poutput(tags)
set_data_parser = argparse.ArgumentParser() set_data_parser = argparse.ArgumentParser()
set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set') set_data_parser.add_argument(
set_data_parser.add_argument('data', help='Data bytes (hex format) to write') 'tag', type=auto_int, help='BER-TLV Tag of value to set')
set_data_parser.add_argument(
'data', help='Data bytes (hex format) to write')
@cmd2.with_argparser(set_data_parser) @cmd2.with_argparser(set_data_parser)
def do_set_data(self, opts): def do_set_data(self, opts):
"""Set (Write) data for a given tag in a BER-TLV EF""" """Set (Write) data for a given tag in a BER-TLV EF"""
@@ -1041,7 +1098,9 @@ class BerTlvEF(CardEF):
self._cmd.poutput(data) self._cmd.poutput(data)
del_data_parser = argparse.ArgumentParser() del_data_parser = argparse.ArgumentParser()
del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set') del_data_parser.add_argument(
'tag', type=auto_int, help='BER-TLV Tag of value to set')
@cmd2.with_argparser(del_data_parser) @cmd2.with_argparser(del_data_parser)
def do_delete_data(self, opts): def do_delete_data(self, opts):
"""Delete data for a given tag in a BER-TLV EF""" """Delete data for a given tag in a BER-TLV EF"""
@@ -1049,9 +1108,8 @@ class BerTlvEF(CardEF):
if data: if data:
self._cmd.poutput(data) self._cmd.poutput(data)
def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None,
def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None, size={1, None}):
size={1,None}):
""" """
Args: Args:
fid : File Identifier (4 hex digits) fid : File Identifier (4 hex digits)
@@ -1069,7 +1127,8 @@ class BerTlvEF(CardEF):
class RuntimeState(object): class RuntimeState(object):
"""Represent the runtime state of a session with a card.""" """Represent the runtime state of a session with a card."""
def __init__(self, card, profile:'CardProfile'):
def __init__(self, card, profile: 'CardProfile'):
""" """
Args: Args:
card : pysim.cards.Card instance card : pysim.cards.Card instance
@@ -1082,7 +1141,8 @@ class RuntimeState(object):
# make sure the class and selection control bytes, which are specified # make sure the class and selection control bytes, which are specified
# by the card profile are used # by the card profile are used
self.card.set_apdu_parameter(cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl) self.card.set_apdu_parameter(
cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl)
# add application ADFs + MF-files from profile # add application ADFs + MF-files from profile
apps = self._match_applications() apps = self._match_applications()
@@ -1170,7 +1230,7 @@ class RuntimeState(object):
node = node.parent node = node.parent
return None return None
def interpret_sw(self, sw:str): def interpret_sw(self, sw: str):
"""Interpret a given status word relative to the currently selected application """Interpret a given status word relative to the currently selected application
or the underlying card profile. or the underlying card profile.
@@ -1191,11 +1251,12 @@ class RuntimeState(object):
res = app.interpret_sw(sw) res = app.interpret_sw(sw)
return res or self.profile.interpret_sw(sw) return res or self.profile.interpret_sw(sw)
def probe_file(self, fid:str, cmd_app=None): def probe_file(self, fid: str, cmd_app=None):
"""Blindly try to select a file and automatically add a matching file """Blindly try to select a file and automatically add a matching file
object if the file actually exists.""" object if the file actually exists."""
if not is_hex(fid, 4, 4): if not is_hex(fid, 4, 4):
raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid) raise ValueError(
"Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid)
try: try:
(data, sw) = self.card._scc.select_file(fid) (data, sw) = self.card._scc.select_file(fid)
@@ -1207,18 +1268,21 @@ class RuntimeState(object):
select_resp = self.selected_file.decode_select_response(data) select_resp = self.selected_file.decode_select_response(data)
if (select_resp['file_descriptor']['file_type'] == 'df'): if (select_resp['file_descriptor']['file_type'] == 'df'):
f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime") f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(),
desc="dedicated file, manually added at runtime")
else: else:
if (select_resp['file_descriptor']['structure'] == 'transparent'): if (select_resp['file_descriptor']['structure'] == 'transparent'):
f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime") f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
desc="elementary file, manually added at runtime")
else: else:
f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime") f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(),
desc="elementary file, manually added at runtime")
self.selected_file.add_files([f]) self.selected_file.add_files([f])
self.selected_file = f self.selected_file = f
return select_resp return select_resp
def select(self, name:str, cmd_app=None): def select(self, name: str, cmd_app=None):
"""Select a file (EF, DF, ADF, MF, ...). """Select a file (EF, DF, ADF, MF, ...).
Args: Args:
@@ -1265,14 +1329,14 @@ class RuntimeState(object):
(data, sw) = self.card._scc.status() (data, sw) = self.card._scc.status()
return self.selected_file.decode_select_response(data) return self.selected_file.decode_select_response(data)
def activate_file(self, name:str): def activate_file(self, name: str):
"""Request ACTIVATE FILE of specified file.""" """Request ACTIVATE FILE of specified file."""
sels = self.selected_file.get_selectables() sels = self.selected_file.get_selectables()
f = sels[name] f = sels[name]
data, sw = self.card._scc.activate_file(f.fid) data, sw = self.card._scc.activate_file(f.fid)
return data, sw return data, sw
def read_binary(self, length:int=None, offset:int=0): def read_binary(self, length: int = None, offset: int = 0):
"""Read [part of] a transparent EF binary data. """Read [part of] a transparent EF binary data.
Args: Args:
@@ -1298,7 +1362,7 @@ class RuntimeState(object):
dec_data = self.selected_file.decode_hex(data) dec_data = self.selected_file.decode_hex(data)
return (dec_data, sw) return (dec_data, sw)
def update_binary(self, data_hex:str, offset:int=0): def update_binary(self, data_hex: str, offset: int = 0):
"""Update transparent EF binary data. """Update transparent EF binary data.
Args: Args:
@@ -1309,7 +1373,7 @@ class RuntimeState(object):
raise TypeError("Only works with TransparentEF") raise TypeError("Only works with TransparentEF")
return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write) return self.card._scc.update_binary(self.selected_file.fid, data_hex, offset, conserve=self.conserve_write)
def update_binary_dec(self, data:dict): def update_binary_dec(self, data: dict):
"""Update transparent EF from abstract data. Encodes the data to binary and """Update transparent EF from abstract data. Encodes the data to binary and
then updates the EF with it. then updates the EF with it.
@@ -1319,7 +1383,7 @@ class RuntimeState(object):
data_hex = self.selected_file.encode_hex(data) data_hex = self.selected_file.encode_hex(data)
return self.update_binary(data_hex) return self.update_binary(data_hex)
def read_record(self, rec_nr:int=0): def read_record(self, rec_nr: int = 0):
"""Read a record as binary data. """Read a record as binary data.
Args: Args:
@@ -1332,7 +1396,7 @@ class RuntimeState(object):
# returns a string of hex nibbles # returns a string of hex nibbles
return self.card._scc.read_record(self.selected_file.fid, rec_nr) return self.card._scc.read_record(self.selected_file.fid, rec_nr)
def read_record_dec(self, rec_nr:int=0) -> Tuple[dict, str]: def read_record_dec(self, rec_nr: int = 0) -> Tuple[dict, str]:
"""Read a record and decode it to abstract data. """Read a record and decode it to abstract data.
Args: Args:
@@ -1343,7 +1407,7 @@ class RuntimeState(object):
(data, sw) = self.read_record(rec_nr) (data, sw) = self.read_record(rec_nr)
return (self.selected_file.decode_record_hex(data), sw) return (self.selected_file.decode_record_hex(data), sw)
def update_record(self, rec_nr:int, data_hex:str): def update_record(self, rec_nr: int, data_hex: str):
"""Update a record with given binary data """Update a record with given binary data
Args: Args:
@@ -1354,7 +1418,7 @@ class RuntimeState(object):
raise TypeError("Only works with Linear Fixed EF") raise TypeError("Only works with Linear Fixed EF")
return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex, conserve=self.conserve_write) return self.card._scc.update_record(self.selected_file.fid, rec_nr, data_hex, conserve=self.conserve_write)
def update_record_dec(self, rec_nr:int, data:dict): def update_record_dec(self, rec_nr: int, data: dict):
"""Update a record with given abstract data. Will encode abstract to binary data """Update a record with given abstract data. Will encode abstract to binary data
and then write it to the given record on the card. and then write it to the given record on the card.
@@ -1365,7 +1429,7 @@ class RuntimeState(object):
data_hex = self.selected_file.encode_record_hex(data) data_hex = self.selected_file.encode_record_hex(data)
return self.update_record(rec_nr, data_hex) return self.update_record(rec_nr, data_hex)
def retrieve_data(self, tag:int=0): def retrieve_data(self, tag: int = 0):
"""Read a DO/TLV as binary data. """Read a DO/TLV as binary data.
Args: Args:
@@ -1390,7 +1454,7 @@ class RuntimeState(object):
tag, length, value, remainder = bertlv_parse_one(h2b(data)) tag, length, value, remainder = bertlv_parse_one(h2b(data))
return list(value) return list(value)
def set_data(self, tag:int, data_hex:str): def set_data(self, tag: int, data_hex: str):
"""Update a TLV/DO with given binary data """Update a TLV/DO with given binary data
Args: Args:
@@ -1408,15 +1472,15 @@ class RuntimeState(object):
cmd_app.unregister_command_set(c) cmd_app.unregister_command_set(c)
class FileData(object): class FileData(object):
"""Represent the runtime, on-card data.""" """Represent the runtime, on-card data."""
def __init__(self, fdesc): def __init__(self, fdesc):
self.desc = fdesc self.desc = fdesc
self.fcp = None self.fcp = None
def interpret_sw(sw_data:dict, sw:str): def interpret_sw(sw_data: dict, sw: str):
"""Interpret a given status word. """Interpret a given status word.
Args: Args:
@@ -1435,10 +1499,12 @@ def interpret_sw(sw_data:dict, sw:str):
return (class_str, descr) return (class_str, descr)
return None return None
class CardApplication(object): class CardApplication(object):
"""A card application is represented by an ADF (with contained hierarchy) and optionally """A card application is represented by an ADF (with contained hierarchy) and optionally
some SW definitions.""" some SW definitions."""
def __init__(self, name, adf:Optional[CardADF]=None, aid:str=None, sw:dict=None):
def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None):
""" """
Args: Args:
adf : ADF name adf : ADF name
@@ -1477,11 +1543,11 @@ class CardModel(abc.ABC):
@classmethod @classmethod
@abc.abstractmethod @abc.abstractmethod
def add_files(cls, rs:RuntimeState): def add_files(cls, rs: RuntimeState):
"""Add model specific files to given RuntimeState.""" """Add model specific files to given RuntimeState."""
@classmethod @classmethod
def match(cls, scc:SimCardCommands) -> bool: def match(cls, scc: SimCardCommands) -> bool:
"""Test if given card matches this model.""" """Test if given card matches this model."""
card_atr = scc.get_atr() card_atr = scc.get_atr()
for atr in cls._atrs: for atr in cls._atrs:
@@ -1492,7 +1558,7 @@ class CardModel(abc.ABC):
return False return False
@staticmethod @staticmethod
def apply_matching_models(scc:SimCardCommands, rs:RuntimeState): def apply_matching_models(scc: SimCardCommands, rs: RuntimeState):
"""Check if any of the CardModel sub-classes 'match' the currently inserted card """Check if any of the CardModel sub-classes 'match' the currently inserted card
(by ATR or overriding the 'match' method). If so, call their 'add_files' (by ATR or overriding the 'match' method). If so, call their 'add_files'
method.""" method."""

View File

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

View File

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

View File

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

View File

@@ -27,7 +27,8 @@ from pySim.utils import all_subclasses
import abc import abc
import operator import operator
def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool:
def _mf_select_test(scc: SimCardCommands, cla_byte: str, sel_ctrl: str) -> bool:
cla_byte_bak = scc.cla_byte cla_byte_bak = scc.cla_byte
sel_ctrl_bak = scc.sel_ctrl sel_ctrl_bak = scc.sel_ctrl
scc.reset_card() scc.reset_card()
@@ -45,19 +46,22 @@ def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool:
scc.sel_ctrl = sel_ctrl_bak scc.sel_ctrl = sel_ctrl_bak
return rc return rc
def match_uicc(scc:SimCardCommands) -> bool:
def match_uicc(scc: SimCardCommands) -> bool:
""" Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the """ Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the
card is considered a UICC card. card is considered a UICC card.
""" """
return _mf_select_test(scc, "00", "0004") return _mf_select_test(scc, "00", "0004")
def match_sim(scc:SimCardCommands) -> bool:
def match_sim(scc: SimCardCommands) -> bool:
""" Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card """ Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card
is also a simcard. This will be the case for most UICC cards, but there may is also a simcard. This will be the case for most UICC cards, but there may
also be plain UICC cards without 2G support as well. also be plain UICC cards without 2G support as well.
""" """
return _mf_select_test(scc, "a0", "0000") return _mf_select_test(scc, "a0", "0000")
class CardProfile(object): class CardProfile(object):
"""A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of """A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of
applications as well as profile-specific SW and shell commands. Every card has applications as well as profile-specific SW and shell commands. Every card has
@@ -86,7 +90,7 @@ class CardProfile(object):
def __str__(self): def __str__(self):
return self.name return self.name
def add_application(self, app:CardApplication): def add_application(self, app: CardApplication):
"""Add an application to a card profile. """Add an application to a card profile.
Args: Args:
@@ -94,7 +98,7 @@ class CardProfile(object):
""" """
self.applications.append(app) self.applications.append(app)
def interpret_sw(self, sw:str): def interpret_sw(self, sw: str):
"""Interpret a given status word within the profile. """Interpret a given status word within the profile.
Args: Args:
@@ -106,7 +110,7 @@ class CardProfile(object):
return interpret_sw(self.sw, sw) return interpret_sw(self.sw, sw)
@staticmethod @staticmethod
def decode_select_response(data_hex:str) -> object: def decode_select_response(data_hex: str) -> object:
"""Decode the response to a SELECT command. """Decode the response to a SELECT command.
This is the fall-back method which doesn't perform any decoding. It mostly This is the fall-back method which doesn't perform any decoding. It mostly
@@ -121,7 +125,7 @@ class CardProfile(object):
@staticmethod @staticmethod
@abc.abstractmethod @abc.abstractmethod
def match_with_card(scc:SimCardCommands) -> bool: def match_with_card(scc: SimCardCommands) -> bool:
"""Check if the specific profile matches the card. This method is a """Check if the specific profile matches the card. This method is a
placeholder that is overloaded by specific dirived classes. The method placeholder that is overloaded by specific dirived classes. The method
actively probes the card to make sure the profile class matches the actively probes the card to make sure the profile class matches the
@@ -137,7 +141,7 @@ class CardProfile(object):
return False return False
@staticmethod @staticmethod
def pick(scc:SimCardCommands): def pick(scc: SimCardCommands):
profiles = list(all_subclasses(CardProfile)) profiles = list(all_subclasses(CardProfile))
profiles.sort(key=operator.attrgetter('ORDER')) profiles.sort(key=operator.attrgetter('ORDER'))

View File

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

View File

@@ -32,6 +32,7 @@ from pySim.exceptions import *
import inspect import inspect
import abc import abc
class TlvMeta(abc.ABCMeta): class TlvMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass. """Metaclass which we use to set some class variables at the time of defining a subclass.
This allows us to create subclasses for each TLV/IE type, where the class represents fixed This allows us to create subclasses for each TLV/IE type, where the class represents fixed
@@ -54,6 +55,7 @@ class TlvMeta(abc.ABCMeta):
x.nested_collection_cls = cls x.nested_collection_cls = cls
return x return x
class TlvCollectionMeta(abc.ABCMeta): class TlvCollectionMeta(abc.ABCMeta):
"""Metaclass which we use to set some class variables at the time of defining a subclass. """Metaclass which we use to set some class variables at the time of defining a subclass.
This allows us to create subclasses for each Collection type, where the class represents fixed This allows us to create subclasses for each Collection type, where the class represents fixed
@@ -72,6 +74,7 @@ class Transcodable(abc.ABC):
* via a 'construct' object stored in a derived class' _construct variable, or * via a 'construct' object stored in a derived class' _construct variable, or
* via a 'construct' object stored in an instance _construct variable, or * via a 'construct' object stored in an instance _construct variable, or
* via a derived class' _{to,from}_bytes() methods.""" * via a derived class' _{to,from}_bytes() methods."""
def __init__(self): def __init__(self):
self.encoded = None self.encoded = None
self.decoded = None self.decoded = None
@@ -95,7 +98,7 @@ class Transcodable(abc.ABC):
def _to_bytes(self): def _to_bytes(self):
raise NotImplementedError raise NotImplementedError
def from_bytes(self, do:bytes): def from_bytes(self, do: bytes):
"""Convert from binary bytes to internal representation. Store the decoded result """Convert from binary bytes to internal representation. Store the decoded result
in the internal state and return it.""" in the internal state and return it."""
self.encoded = do self.encoded = do
@@ -110,9 +113,10 @@ class Transcodable(abc.ABC):
return self.decoded return self.decoded
# not an abstractmethod, as it is only required if no _construct exists # not an abstractmethod, as it is only required if no _construct exists
def _from_bytes(self, do:bytes): def _from_bytes(self, do: bytes):
raise NotImplementedError raise NotImplementedError
class IE(Transcodable, metaclass=TlvMeta): class IE(Transcodable, metaclass=TlvMeta):
# we specify the metaclass so any downstream subclasses will automatically use it # we specify the metaclass so any downstream subclasses will automatically use it
"""Base class for various Information Elements. We understand the notion of a hierarchy """Base class for various Information Elements. We understand the notion of a hierarchy
@@ -146,7 +150,7 @@ class IE(Transcodable, metaclass=TlvMeta):
v = self.decoded v = self.decoded
return {type(self).__name__: v} return {type(self).__name__: v}
def from_dict(self, decoded:dict): def from_dict(self, decoded: dict):
"""Set the IE internal decoded representation to data from the argument. """Set the IE internal decoded representation to data from the argument.
If this is a nested IE, the child IE instance list is re-created.""" If this is a nested IE, the child IE instance list is re-created."""
if self.nested_collection: if self.nested_collection:
@@ -177,7 +181,7 @@ class IE(Transcodable, metaclass=TlvMeta):
else: else:
return super().to_bytes() return super().to_bytes()
def from_bytes(self, do:bytes): def from_bytes(self, do: bytes):
"""Parse _the value part_ from binary bytes to internal representation.""" """Parse _the value part_ from binary bytes to internal representation."""
if self.nested_collection: if self.nested_collection:
self.children = self.nested_collection.from_bytes(do) self.children = self.nested_collection.from_bytes(do)
@@ -188,6 +192,7 @@ class IE(Transcodable, metaclass=TlvMeta):
class TLV_IE(IE): class TLV_IE(IE):
"""Abstract base class for various TLV type Information Elements.""" """Abstract base class for various TLV type Information Elements."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -197,12 +202,12 @@ class TLV_IE(IE):
@classmethod @classmethod
@abc.abstractmethod @abc.abstractmethod
def _parse_tag_raw(cls, do:bytes) -> Tuple[int, bytes]: def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
"""Obtain the raw TAG at the start of the bytes provided by the user.""" """Obtain the raw TAG at the start of the bytes provided by the user."""
@classmethod @classmethod
@abc.abstractmethod @abc.abstractmethod
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]: def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
"""Obtain the length encoded at the start of the bytes provided by the user.""" """Obtain the length encoded at the start of the bytes provided by the user."""
@abc.abstractmethod @abc.abstractmethod
@@ -210,7 +215,7 @@ class TLV_IE(IE):
"""Encode the tag part. Must be provided by derived (TLV format specific) class.""" """Encode the tag part. Must be provided by derived (TLV format specific) class."""
@abc.abstractmethod @abc.abstractmethod
def _encode_len(self, val:bytes) -> bytes: def _encode_len(self, val: bytes) -> bytes:
"""Encode the length part assuming a certain binary value. Must be provided by """Encode the length part assuming a certain binary value. Must be provided by
derived (TLV format specific) class.""" derived (TLV format specific) class."""
@@ -222,7 +227,7 @@ class TLV_IE(IE):
val = self.to_bytes() val = self.to_bytes()
return self._encode_tag() + self._encode_len(val) + val return self._encode_tag() + self._encode_len(val) + val
def from_tlv(self, do:bytes): def from_tlv(self, do: bytes):
(rawtag, remainder) = self.__class__._parse_tag_raw(do) (rawtag, remainder) = self.__class__._parse_tag_raw(do)
if rawtag: if rawtag:
if rawtag != self.tag: if rawtag != self.tag:
@@ -240,50 +245,52 @@ class TLV_IE(IE):
class BER_TLV_IE(TLV_IE): class BER_TLV_IE(TLV_IE):
"""TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2.""" """TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@classmethod @classmethod
def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]: def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
return bertlv_parse_tag(do) return bertlv_parse_tag(do)
@classmethod @classmethod
def _parse_tag_raw(cls, do:bytes) -> Tuple[int, bytes]: def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_tag_raw(do) return bertlv_parse_tag_raw(do)
@classmethod @classmethod
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]: def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_len(do) return bertlv_parse_len(do)
def _encode_tag(self) -> bytes: def _encode_tag(self) -> bytes:
return bertlv_encode_tag(self._compute_tag()) return bertlv_encode_tag(self._compute_tag())
def _encode_len(self, val:bytes) -> bytes: def _encode_len(self, val: bytes) -> bytes:
return bertlv_encode_len(len(val)) return bertlv_encode_len(len(val))
class COMPR_TLV_IE(TLV_IE): class COMPR_TLV_IE(TLV_IE):
"""TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220.""" """TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220."""
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.comprehension = False self.comprehension = False
@classmethod @classmethod
def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]: def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]:
return comprehensiontlv_parse_tag(do) return comprehensiontlv_parse_tag(do)
@classmethod @classmethod
def _parse_tag_raw(cls, do:bytes) -> Tuple[int, bytes]: def _parse_tag_raw(cls, do: bytes) -> Tuple[int, bytes]:
return comprehensiontlv_parse_tag_raw(do) return comprehensiontlv_parse_tag_raw(do)
@classmethod @classmethod
def _parse_len(cls, do:bytes) -> Tuple[int, bytes]: def _parse_len(cls, do: bytes) -> Tuple[int, bytes]:
return bertlv_parse_len(do) return bertlv_parse_len(do)
def _encode_tag(self) -> bytes: def _encode_tag(self) -> bytes:
return comprehensiontlv_encode_tag(self._compute_tag()) return comprehensiontlv_encode_tag(self._compute_tag())
def _encode_len(self, val:bytes) -> bytes: def _encode_len(self, val: bytes) -> bytes:
return bertlv_encode_len(len(val)) return bertlv_encode_len(len(val))
@@ -294,14 +301,15 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
of each DO.""" of each DO."""
# this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses # this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses
possible_nested = [] possible_nested = []
def __init__(self, desc=None, **kwargs): def __init__(self, desc=None, **kwargs):
self.desc = desc self.desc = desc
#print("possible_nested: ", self.possible_nested) #print("possible_nested: ", self.possible_nested)
self.members = kwargs.get('nested', self.possible_nested) self.members = kwargs.get('nested', self.possible_nested)
self.members_by_tag = {} self.members_by_tag = {}
self.members_by_name = {} self.members_by_name = {}
self.members_by_tag = { m.tag:m for m in self.members } self.members_by_tag = {m.tag: m for m in self.members}
self.members_by_name = { m.__name__:m for m in self.members } self.members_by_name = {m.__name__: m for m in self.members}
# if we are a constructed IE, [ordered] list of actual child-IE instances # if we are a constructed IE, [ordered] list of actual child-IE instances
self.children = kwargs.get('children', []) self.children = kwargs.get('children', [])
self.encoded = None self.encoded = None
@@ -322,11 +330,11 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
return TLV_IE_Collection(self.desc, nested=members) return TLV_IE_Collection(self.desc, nested=members)
elif inspect.isclass(other) and issubclass(other, TLV_IE): elif inspect.isclass(other) and issubclass(other, TLV_IE):
# adding a member to a collection # adding a member to a collection
return TLV_IE_Collection(self.desc, nested = self.members + [other]) return TLV_IE_Collection(self.desc, nested=self.members + [other])
else: else:
raise TypeError raise TypeError
def from_bytes(self, binary:bytes) -> List[TLV_IE]: def from_bytes(self, binary: bytes) -> List[TLV_IE]:
"""Create a list of TLV_IEs from the collection based on binary input data. """Create a list of TLV_IEs from the collection based on binary input data.
Args: Args:
binary : binary bytes of encoded data binary : binary bytes of encoded data
@@ -353,9 +361,9 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
else: else:
# unknown tag; create the related class on-the-fly using the same base class # unknown tag; create the related class on-the-fly using the same base class
name = 'unknown_%s_%X' % (first.__base__.__name__, tag) name = 'unknown_%s_%X' % (first.__base__.__name__, tag)
cls = type(name, (first.__base__,), {'tag':tag, 'possible_nested':[], cls = type(name, (first.__base__,), {'tag': tag, 'possible_nested': [],
'nested_collection_cls':None}) 'nested_collection_cls': None})
cls._from_bytes = lambda s, a : {'raw': a.hex()} cls._from_bytes = lambda s, a: {'raw': a.hex()}
cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw']) cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw'])
# create an instance and parse accordingly # create an instance and parse accordingly
inst = cls() inst = cls()
@@ -364,7 +372,7 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta):
self.children = res self.children = res
return res return res
def from_dict(self, decoded:List[dict]) -> List[TLV_IE]: def from_dict(self, decoded: List[dict]) -> List[TLV_IE]:
"""Create a list of TLV_IE instances from the collection based on an array """Create a list of TLV_IE instances from the collection based on an array
of dicts, where they key indicates the name of the TLV_IE subclass to use.""" of dicts, where they key indicates the name of the TLV_IE subclass to use."""
# list of instances of TLV_IE collection member classes appearing in the data # list of instances of TLV_IE collection member classes appearing in the data

View File

@@ -29,6 +29,7 @@ from pySim.utils import sw_match, b2h, h2b, i2h
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
class ApduTracer: class ApduTracer:
def trace_command(self, cmd): def trace_command(self, cmd):
pass pass
@@ -45,7 +46,7 @@ class LinkBase(abc.ABC):
self.apdu_tracer = apdu_tracer self.apdu_tracer = apdu_tracer
@abc.abstractmethod @abc.abstractmethod
def _send_apdu_raw(self, pdu:str) -> Tuple[str, str]: def _send_apdu_raw(self, pdu: str) -> Tuple[str, str]:
"""Implementation specific method for sending the PDU.""" """Implementation specific method for sending the PDU."""
def set_sw_interpreter(self, interp): def set_sw_interpreter(self, interp):
@@ -53,7 +54,7 @@ class LinkBase(abc.ABC):
self.sw_interpreter = interp self.sw_interpreter = interp
@abc.abstractmethod @abc.abstractmethod
def wait_for_card(self, timeout:int=None, newcardonly:bool=False): def wait_for_card(self, timeout: int = None, newcardonly: bool = False):
"""Wait for a card and connect to it """Wait for a card and connect to it
Args: Args:
@@ -76,7 +77,7 @@ class LinkBase(abc.ABC):
"""Resets the card (power down/up) """Resets the card (power down/up)
""" """
def send_apdu_raw(self, pdu:str): def send_apdu_raw(self, pdu: str):
"""Sends an APDU with minimal processing """Sends an APDU with minimal processing
Args: Args:
@@ -105,7 +106,7 @@ class LinkBase(abc.ABC):
""" """
data, sw = self.send_apdu_raw(pdu) data, sw = self.send_apdu_raw(pdu)
# When whe have sent the first APDU, the SW may indicate that there are response bytes # When we have sent the first APDU, the SW may indicate that there are response bytes
# available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where # available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
# xx is the number of response bytes available. # xx is the number of response bytes available.
# See also: # See also:
@@ -118,7 +119,7 @@ class LinkBase(abc.ABC):
if sw[0:2] == '6c': if sw[0:2] == '6c':
# SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding # SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding
pdu_gr = pdu[0:8] + sw[2:4] pdu_gr = pdu[0:8] + sw[2:4]
data,sw = self.send_apdu_raw(pdu_gr) data, sw = self.send_apdu_raw(pdu_gr)
return data, sw return data, sw
@@ -185,11 +186,13 @@ class LinkBase(abc.ABC):
Returns: Returns:
Tuple of (decoded_data, sw) Tuple of (decoded_data, sw)
""" """
(rsp, sw) = self.send_apdu_constr(cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr) (rsp, sw) = self.send_apdu_constr(cla, ins,
p1, p2, cmd_constr, cmd_data, resp_constr)
if not sw_match(sw, sw_exp): if not sw_match(sw, sw_exp):
raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter) raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter)
return (rsp, sw) return (rsp, sw)
def argparse_add_reader_args(arg_parser): def argparse_add_reader_args(arg_parser):
"""Add all reader related arguments to the given argparse.Argumentparser instance.""" """Add all reader related arguments to the given argparse.Argumentparser instance."""
serial_group = arg_parser.add_argument_group('Serial Reader') serial_group = arg_parser.add_argument_group('Serial Reader')
@@ -214,6 +217,7 @@ def argparse_add_reader_args(arg_parser):
return arg_parser return arg_parser
def init_reader(opts, **kwargs) -> Optional[LinkBase]: def init_reader(opts, **kwargs) -> Optional[LinkBase]:
""" """
Init card reader driver Init card reader driver
@@ -231,15 +235,18 @@ def init_reader(opts, **kwargs) -> Optional[LinkBase]:
elif opts.modem_dev is not None: elif opts.modem_dev is not None:
print("Using modem for Generic SIM Access (3GPP TS 27.007)") print("Using modem for Generic SIM Access (3GPP TS 27.007)")
from pySim.transport.modem_atcmd import ModemATCommandLink from pySim.transport.modem_atcmd import ModemATCommandLink
sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs) sl = ModemATCommandLink(
device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs)
else: # Serial reader is default else: # Serial reader is default
print("Using serial reader interface") print("Using serial reader interface")
from pySim.transport.serial import SerialSimLink from pySim.transport.serial import SerialSimLink
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate, **kwargs) sl = SerialSimLink(device=opts.device,
baudrate=opts.baudrate, **kwargs)
return sl return sl
except Exception as e: except Exception as e:
if str(e): if str(e):
print("Card reader initialization failed with exception:\n" + str(e)) print("Card reader initialization failed with exception:\n" + str(e))
else: else:
print("Card reader initialization failed with an exception of type:\n" + str(type(e))) print(
"Card reader initialization failed with an exception of type:\n" + str(type(e)))
return None return None

View File

@@ -25,6 +25,7 @@ from pySim.transport import LinkBase
from pySim.exceptions import * from pySim.exceptions import *
from pySim.utils import h2b, b2h from pySim.utils import h2b, b2h
class L1CTLMessage(object): class L1CTLMessage(object):
# Every (encoded) L1CTL message has the following structure: # Every (encoded) L1CTL message has the following structure:
@@ -35,13 +36,14 @@ class L1CTLMessage(object):
# - padding (2 spare bytes) # - padding (2 spare bytes)
# - ... payload ... # - ... payload ...
def __init__(self, msg_type, flags = 0x00): def __init__(self, msg_type, flags=0x00):
# Init L1CTL message header # Init L1CTL message header
self.data = struct.pack("BBxx", msg_type, flags) self.data = struct.pack("BBxx", msg_type, flags)
def gen_msg(self): def gen_msg(self):
return struct.pack("!H", len(self.data)) + self.data return struct.pack("!H", len(self.data)) + self.data
class L1CTLMessageReset(L1CTLMessage): class L1CTLMessageReset(L1CTLMessage):
# L1CTL message types # L1CTL message types
@@ -54,10 +56,11 @@ class L1CTLMessageReset(L1CTLMessage):
L1CTL_RES_T_FULL = 0x01 L1CTL_RES_T_FULL = 0x01
L1CTL_RES_T_SCHED = 0x02 L1CTL_RES_T_SCHED = 0x02
def __init__(self, type = L1CTL_RES_T_FULL): def __init__(self, type=L1CTL_RES_T_FULL):
super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ) super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ)
self.data += struct.pack("Bxxx", type) self.data += struct.pack("Bxxx", type)
class L1CTLMessageSIM(L1CTLMessage): class L1CTLMessageSIM(L1CTLMessage):
# SIM related message types # SIM related message types
@@ -68,14 +71,16 @@ class L1CTLMessageSIM(L1CTLMessage):
super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ) super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ)
self.data += pdu self.data += pdu
class CalypsoSimLink(LinkBase): class CalypsoSimLink(LinkBase):
"""Transport Link for Calypso based phones.""" """Transport Link for Calypso based phones."""
def __init__(self, sock_path:str = "/tmp/osmocom_l2", **kwargs): def __init__(self, sock_path: str = "/tmp/osmocom_l2", **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
# Make sure that a given socket path exists # Make sure that a given socket path exists
if not os.path.exists(sock_path): if not os.path.exists(sock_path):
raise ReaderError("There is no such ('%s') UNIX socket" % sock_path) raise ReaderError(
"There is no such ('%s') UNIX socket" % sock_path)
print("Connecting to osmocon at '%s'..." % sock_path) print("Connecting to osmocon at '%s'..." % sock_path)
@@ -86,7 +91,7 @@ class CalypsoSimLink(LinkBase):
def __del__(self): def __del__(self):
self.sock.close() self.sock.close()
def wait_for_rsp(self, exp_len = 128): def wait_for_rsp(self, exp_len=128):
# Wait for incoming data (timeout is 3 seconds) # Wait for incoming data (timeout is 3 seconds)
s, _, _ = select.select([self.sock], [], [], 3.0) s, _, _ = select.select([self.sock], [], [], 3.0)
if not s: if not s:
@@ -113,7 +118,7 @@ class CalypsoSimLink(LinkBase):
def disconnect(self): def disconnect(self):
pass # Nothing to do really ... pass # Nothing to do really ...
def wait_for_card(self, timeout = None, newcardonly = False): def wait_for_card(self, timeout=None, newcardonly=False):
pass # Nothing to do really ... pass # Nothing to do really ...
def _send_apdu_raw(self, pdu): def _send_apdu_raw(self, pdu):

View File

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

View File

@@ -30,7 +30,7 @@ from pySim.utils import h2i, i2h
class PcscSimLink(LinkBase): class PcscSimLink(LinkBase):
""" pySim: PCSC reader transport link.""" """ pySim: PCSC reader transport link."""
def __init__(self, reader_number:int=0, **kwargs): def __init__(self, reader_number: int = 0, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
r = readers() r = readers()
if reader_number >= len(r): if reader_number >= len(r):
@@ -46,8 +46,9 @@ class PcscSimLink(LinkBase):
pass pass
return return
def wait_for_card(self, timeout:int=None, newcardonly:bool=False): def wait_for_card(self, timeout: int = None, newcardonly: bool = False):
cr = CardRequest(readers=[self._reader], timeout=timeout, newcardonly=newcardonly) cr = CardRequest(readers=[self._reader],
timeout=timeout, newcardonly=newcardonly)
try: try:
cr.waitforcard() cr.waitforcard()
except CardRequestTimeoutException: except CardRequestTimeoutException:

View File

@@ -28,20 +28,20 @@ from pySim.utils import h2b, b2h
class SerialSimLink(LinkBase): class SerialSimLink(LinkBase):
""" pySim: Transport Link for serial (RS232) based readers included with simcard""" """ pySim: Transport Link for serial (RS232) based readers included with simcard"""
def __init__(self, device:str='/dev/ttyUSB0', baudrate:int=9600, rst:str='-rts', def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 9600, rst: str = '-rts',
debug:bool=False, **kwargs): debug: bool = False, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
if not os.path.exists(device): if not os.path.exists(device):
raise ValueError("device file %s does not exist -- abort" % device) raise ValueError("device file %s does not exist -- abort" % device)
self._sl = serial.Serial( self._sl = serial.Serial(
port = device, port=device,
parity = serial.PARITY_EVEN, parity=serial.PARITY_EVEN,
bytesize = serial.EIGHTBITS, bytesize=serial.EIGHTBITS,
stopbits = serial.STOPBITS_TWO, stopbits=serial.STOPBITS_TWO,
timeout = 1, timeout=1,
xonxoff = 0, xonxoff=0,
rtscts = 0, rtscts=0,
baudrate = baudrate, baudrate=baudrate,
) )
self._rst_pin = rst self._rst_pin = rst
self._debug = debug self._debug = debug
@@ -111,7 +111,7 @@ class SerialSimLink(LinkBase):
'rts': self._sl.setRTS, 'rts': self._sl.setRTS,
'dtr': self._sl.setDTR, 'dtr': self._sl.setDTR,
} }
rst_val_map = { '+':0, '-':1 } rst_val_map = {'+': 0, '-': 1}
try: try:
rst_meth = rst_meth_map[self._rst_pin[1:]] rst_meth = rst_meth_map[self._rst_pin[1:]]
@@ -168,7 +168,8 @@ class SerialSimLink(LinkBase):
self._sl.write(b) self._sl.write(b)
r = self._sl.read() r = self._sl.read()
if r != b: # TX and RX are tied, so we must clear the echo if r != b: # TX and RX are tied, so we must clear the echo
raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (ord(b), '%02x'%ord(r) if r else '(nil)')) raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (
ord(b), '%02x' % ord(r) if r else '(nil)'))
def _tx_string(self, s): def _tx_string(self, s):
"""This is only safe if it's guaranteed the card won't send any data """This is only safe if it's guaranteed the card won't send any data
@@ -176,7 +177,8 @@ class SerialSimLink(LinkBase):
self._sl.write(s) self._sl.write(s)
r = self._sl.read(len(s)) r = self._sl.read(len(s))
if r != s: # TX and RX are tied, so we must clear the echo if r != s: # TX and RX are tied, so we must clear the echo
raise ProtocolError("Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r))) raise ProtocolError(
"Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r)))
def _rx_byte(self): def _rx_byte(self):
return self._sl.read() return self._sl.read()

View File

@@ -76,7 +76,7 @@ ts_102_22x_cmdset = CardCommandSet('TS 102 22x', [
CardCommand('TERMINATE EF', 0xE8, ['0X', '4X']), CardCommand('TERMINATE EF', 0xE8, ['0X', '4X']),
CardCommand('TERMINATE CARD USAGE', 0xFE, ['0X', '4X']), CardCommand('TERMINATE CARD USAGE', 0xFE, ['0X', '4X']),
CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']), CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']),
]) ])
FCP_TLV_MAP = { FCP_TLV_MAP = {
@@ -92,7 +92,7 @@ FCP_TLV_MAP = {
'80': 'file_size', '80': 'file_size',
'81': 'total_file_size', '81': 'total_file_size',
'88': 'short_file_id', '88': 'short_file_id',
} }
# ETSI TS 102 221 11.1.1.4.6 # ETSI TS 102 221 11.1.1.4.6
FCP_Proprietary_TLV_MAP = { FCP_Proprietary_TLV_MAP = {
@@ -107,9 +107,11 @@ FCP_Proprietary_TLV_MAP = {
'88': 'specific_uicc_env_cond', '88': 'specific_uicc_env_cond',
'89': 'p2p_cat_secured_apdu', '89': 'p2p_cat_secured_apdu',
# Additional private TLV objects (bits b7 and b8 of the first byte of the tag set to '1') # Additional private TLV objects (bits b7 and b8 of the first byte of the tag set to '1')
} }
# ETSI TS 102 221 11.1.1.4.3 # ETSI TS 102 221 11.1.1.4.3
def interpret_file_descriptor(in_hex): def interpret_file_descriptor(in_hex):
in_bin = h2b(in_hex) in_bin = h2b(in_hex)
out = {} out = {}
@@ -140,6 +142,8 @@ def interpret_file_descriptor(in_hex):
return out return out
# ETSI TS 102 221 11.1.1.4.9 # ETSI TS 102 221 11.1.1.4.9
def interpret_life_cycle_sts_int(in_hex): def interpret_life_cycle_sts_int(in_hex):
lcsi = int(in_hex, 16) lcsi = int(in_hex, 16)
if lcsi == 0x00: if lcsi == 0x00:
@@ -157,35 +161,40 @@ def interpret_life_cycle_sts_int(in_hex):
else: else:
return in_hex return in_hex
# ETSI TS 102 221 11.1.1.4.10 # ETSI TS 102 221 11.1.1.4.10
FCP_Pin_Status_TLV_MAP = { FCP_Pin_Status_TLV_MAP = {
'90': 'ps_do', '90': 'ps_do',
'95': 'usage_qualifier', '95': 'usage_qualifier',
'83': 'key_reference', '83': 'key_reference',
} }
def interpret_ps_templ_do(in_hex): def interpret_ps_templ_do(in_hex):
# cannot use the 'TLV' parser due to repeating tags # cannot use the 'TLV' parser due to repeating tags
#psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP) #psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP)
#return psdo_tlv.parse(in_hex) # return psdo_tlv.parse(in_hex)
return in_hex return in_hex
# 'interpreter' functions for each tag # 'interpreter' functions for each tag
FCP_interpreter_map = { FCP_interpreter_map = {
'80': lambda x: int(x, 16), '80': lambda x: int(x, 16),
'82': interpret_file_descriptor, '82': interpret_file_descriptor,
'8A': interpret_life_cycle_sts_int, '8A': interpret_life_cycle_sts_int,
'C6': interpret_ps_templ_do, 'C6': interpret_ps_templ_do,
} }
FCP_prorietary_interpreter_map = { FCP_prorietary_interpreter_map = {
'83': lambda x: int(x, 16), '83': lambda x: int(x, 16),
} }
# pytlv unfortunately doesn't have a setting using which we can make it # pytlv unfortunately doesn't have a setting using which we can make it
# accept unknown tags. It also doesn't raise a specific exception type but # accept unknown tags. It also doesn't raise a specific exception type but
# just the generic ValueError, so we cannot ignore those either. Instead, # just the generic ValueError, so we cannot ignore those either. Instead,
# we insert a dict entry for every possible proprietary tag permitted # we insert a dict entry for every possible proprietary tag permitted
def fixup_fcp_proprietary_tlv_map(tlv_map): def fixup_fcp_proprietary_tlv_map(tlv_map):
if 'D0' in tlv_map: if 'D0' in tlv_map:
return return
@@ -204,6 +213,7 @@ def tlv_key_replace(inmap, indata):
return key return key
return {newkey(inmap, d[0]): d[1] for d in indata.items()} return {newkey(inmap, d[0]): d[1] for d in indata.items()}
def tlv_val_interpret(inmap, indata): def tlv_val_interpret(inmap, indata):
def newval(inmap, key, val): def newval(inmap, key, val):
if key in inmap: if key in inmap:
@@ -214,11 +224,12 @@ def tlv_val_interpret(inmap, indata):
# ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4 # ETSI TS 102 221 Section 9.2.7 + ISO7816-4 9.3.3/9.3.4
class _AM_DO_DF(DataObject): class _AM_DO_DF(DataObject):
def __init__(self): def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80) super().__init__('access_mode', 'Access Mode', tag=0x80)
def from_bytes(self, do:bytes): def from_bytes(self, do: bytes):
res = [] res = []
if len(do) != 1: if len(do) != 1:
raise ValueError("We only support single-byte AMF inside AM-DO") raise ValueError("We only support single-byte AMF inside AM-DO")
@@ -262,10 +273,11 @@ class _AM_DO_DF(DataObject):
class _AM_DO_EF(DataObject): class _AM_DO_EF(DataObject):
"""ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31""" """ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31"""
def __init__(self): def __init__(self):
super().__init__('access_mode', 'Access Mode', tag=0x80) super().__init__('access_mode', 'Access Mode', tag=0x80)
def from_bytes(self, do:bytes): def from_bytes(self, do: bytes):
res = [] res = []
if len(do) != 1: if len(do) != 1:
raise ValueError("We only support single-byte AMF inside AM-DO") raise ValueError("We only support single-byte AMF inside AM-DO")
@@ -306,12 +318,14 @@ class _AM_DO_EF(DataObject):
val |= 0x01 val |= 0x01
return val.to_bytes(1, 'big') return val.to_bytes(1, 'big')
class _AM_DO_CHDR(DataObject): class _AM_DO_CHDR(DataObject):
"""Command Header Access Mode DO according to ISO 7816-4 Table 32.""" """Command Header Access Mode DO according to ISO 7816-4 Table 32."""
def __init__(self, tag): def __init__(self, tag):
super().__init__('command_header', 'Command Header Description', tag=tag) super().__init__('command_header', 'Command Header Description', tag=tag)
def from_bytes(self, do:bytes): def from_bytes(self, do: bytes):
res = {} res = {}
i = 0 i = 0
if self.tag & 0x08: if self.tag & 0x08:
@@ -353,6 +367,7 @@ class _AM_DO_CHDR(DataObject):
res.append(self.decoded['P2']) res.append(self.decoded['P2'])
return res return res
AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[ AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[
_AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84), _AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84),
_AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88), _AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88),
@@ -393,12 +408,15 @@ pin_names = bidict({
0x8c: 'ADM8', 0x8c: 'ADM8',
0x8d: 'ADM9', 0x8d: 'ADM9',
0x8e: 'ADM10', 0x8e: 'ADM10',
}) })
class CRT_DO(DataObject): class CRT_DO(DataObject):
"""Control Reference Template as per TS 102 221 9.5.1""" """Control Reference Template as per TS 102 221 9.5.1"""
def __init__(self): def __init__(self):
super().__init__('control_reference_template', 'Control Reference Template', tag=0xA4) super().__init__('control_reference_template',
'Control Reference Template', tag=0xA4)
def from_bytes(self, do: bytes): def from_bytes(self, do: bytes):
"""Decode a Control Reference Template DO.""" """Decode a Control Reference Template DO."""
@@ -407,7 +425,8 @@ class CRT_DO(DataObject):
if do[0] != 0x83 or do[1] != 0x01: if do[0] != 0x83 or do[1] != 0x01:
raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do) raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do)
if do[3:] != b'\x95\x01\x08': if do[3:] != b'\x95\x01\x08':
raise ValueError('Unsupported Usage Qualifier Tag or Len in CRT DO %s', do) raise ValueError(
'Unsupported Usage Qualifier Tag or Len in CRT DO %s', do)
self.encoded = do[0:6] self.encoded = do[0:6]
self.decoded = pin_names[do[2]] self.decoded = pin_names[do[2]]
return do[6:] return do[6:]
@@ -417,11 +436,13 @@ class CRT_DO(DataObject):
return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08' return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08'
# ISO7816-4 9.3.3 Table 33 # ISO7816-4 9.3.3 Table 33
class SecCondByte_DO(DataObject): class SecCondByte_DO(DataObject):
def __init__(self, tag=0x9d): def __init__(self, tag=0x9d):
super().__init__('security_condition_byte', tag=tag) super().__init__('security_condition_byte', tag=tag)
def from_bytes(self, binary:bytes): def from_bytes(self, binary: bytes):
if len(binary) != 1: if len(binary) != 1:
raise ValueError raise ValueError
inb = binary[0] inb = binary[0]
@@ -440,7 +461,7 @@ class SecCondByte_DO(DataObject):
res.append('external_auth') res.append('external_auth')
if inb & 0x10: if inb & 0x10:
res.append('user_auth') res.append('user_auth')
rd = {'mode': cond } rd = {'mode': cond}
if len(res): if len(res):
rd['conditions'] = res rd['conditions'] = res
self.decoded = rd self.decoded = rd
@@ -470,25 +491,31 @@ class SecCondByte_DO(DataObject):
raise ValueError('Unknown condition %s' % c) raise ValueError('Unknown condition %s' % c)
return res.to_bytes(1, 'big') return res.to_bytes(1, 'big')
Always_DO = TL0_DataObject('always', 'Always', 0x90) Always_DO = TL0_DataObject('always', 'Always', 0x90)
Never_DO = TL0_DataObject('never', 'Never', 0x97) Never_DO = TL0_DataObject('never', 'Never', 0x97)
class Nested_DO(DataObject): class Nested_DO(DataObject):
"""A DO that nests another DO/Choice/Sequence""" """A DO that nests another DO/Choice/Sequence"""
def __init__(self, name, tag, choice): def __init__(self, name, tag, choice):
super().__init__(name, tag=tag) super().__init__(name, tag=tag)
self.children = choice self.children = choice
def from_bytes(self, binary:bytes) -> list:
def from_bytes(self, binary: bytes) -> list:
remainder = binary remainder = binary
self.decoded = [] self.decoded = []
while remainder: while remainder:
rc, remainder = self.children.decode(remainder) rc, remainder = self.children.decode(remainder)
self.decoded.append(rc) self.decoded.append(rc)
return self.decoded return self.decoded
def to_bytes(self) -> bytes: def to_bytes(self) -> bytes:
encoded = [self.children.encode(d) for d in self.decoded] encoded = [self.children.encode(d) for d in self.decoded]
return b''.join(encoded) return b''.join(encoded)
OR_Template = DataObjectChoice('or_template', 'OR-Template', OR_Template = DataObjectChoice('or_template', 'OR-Template',
members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()]) members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()])
OR_DO = Nested_DO('or', 0xa0, OR_Template) OR_DO = Nested_DO('or', 0xa0, OR_Template)
@@ -503,6 +530,8 @@ SC_DO = DataObjectChoice('security_condition', 'Security Condition',
OR_DO, AND_DO, NOT_DO]) OR_DO, AND_DO, NOT_DO])
# TS 102 221 Section 13.1 # TS 102 221 Section 13.1
class EF_DIR(LinFixedEF): class EF_DIR(LinFixedEF):
class ApplicationLabel(BER_TLV_IE, tag=0x50): class ApplicationLabel(BER_TLV_IE, tag=0x50):
# TODO: UCS-2 coding option as per Annex A of TS 102 221 # TODO: UCS-2 coding option as per Annex A of TS 102 221
@@ -518,13 +547,15 @@ class EF_DIR(LinFixedEF):
pass pass
def __init__(self, fid='2f00', sfid=0x1e, name='EF.DIR', desc='Application Directory'): def __init__(self, fid='2f00', sfid=0x1e, name='EF.DIR', desc='Application Directory'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={5,54}) super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={5, 54})
self._tlv = EF_DIR.ApplicationTemplate self._tlv = EF_DIR.ApplicationTemplate
# TS 102 221 Section 13.2 # TS 102 221 Section 13.2
class EF_ICCID(TransparentEF): class EF_ICCID(TransparentEF):
def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'): def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10,10}) super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10, 10})
def _decode_hex(self, raw_hex): def _decode_hex(self, raw_hex):
return {'iccid': dec_iccid(raw_hex)} return {'iccid': dec_iccid(raw_hex)}
@@ -533,14 +564,19 @@ class EF_ICCID(TransparentEF):
return enc_iccid(abstract['iccid']) return enc_iccid(abstract['iccid'])
# TS 102 221 Section 13.3 # TS 102 221 Section 13.3
class EF_PL(TransRecEF): class EF_PL(TransRecEF):
def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'): def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=2, size={2,None}) super().__init__(fid, sfid=sfid, name=name,
desc=desc, rec_len=2, size={2, None})
def _decode_record_bin(self, bin_data): def _decode_record_bin(self, bin_data):
if bin_data == b'\xff\xff': if bin_data == b'\xff\xff':
return None return None
else: else:
return bin_data.decode('ascii') return bin_data.decode('ascii')
def _encode_record_bin(self, in_json): def _encode_record_bin(self, in_json):
if in_json is None: if in_json is None:
return b'\xff\xff' return b'\xff\xff'
@@ -556,7 +592,7 @@ class EF_ARR(LinFixedEF):
self.shell_commands += [self.AddlShellCommands()] self.shell_commands += [self.AddlShellCommands()]
@staticmethod @staticmethod
def flatten(inp:list): def flatten(inp: list):
"""Flatten the somewhat deep/complex/nested data returned from decoder.""" """Flatten the somewhat deep/complex/nested data returned from decoder."""
def sc_abbreviate(sc): def sc_abbreviate(sc):
if 'always' in sc: if 'always' in sc:
@@ -584,7 +620,7 @@ class EF_ARR(LinFixedEF):
cla = None cla = None
cmd = ts_102_22x_cmdset.lookup(ins, cla) cmd = ts_102_22x_cmdset.lookup(ins, cla)
if cmd: if cmd:
name = cmd.name.lower().replace(' ','_') name = cmd.name.lower().replace(' ', '_')
by_mode[name] = sc_abbr by_mode[name] = sc_abbr
else: else:
raise ValueError raise ValueError
@@ -594,7 +630,7 @@ class EF_ARR(LinFixedEF):
def _decode_record_bin(self, raw_bin_data): def _decode_record_bin(self, raw_bin_data):
# we can only guess if we should decode for EF or DF here :( # we can only guess if we should decode for EF or DF here :(
arr_seq = DataObjectSequence('arr', sequence = [AM_DO_EF, SC_DO]) arr_seq = DataObjectSequence('arr', sequence=[AM_DO_EF, SC_DO])
dec = arr_seq.decode_multi(raw_bin_data) dec = arr_seq.decode_multi(raw_bin_data)
# we cannot pass the result through flatten() here, as we don't have a related # we cannot pass the result through flatten() here, as we don't have a related
# 'un-flattening' decoder, and hence would be unable to encode :( # 'un-flattening' decoder, and hence would be unable to encode :(
@@ -628,15 +664,18 @@ class EF_ARR(LinFixedEF):
# TS 102 221 Section 13.6 # TS 102 221 Section 13.6
class EF_UMPC(TransparentEF): class EF_UMPC(TransparentEF):
def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'): def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'):
super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5,5}) super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5, 5})
addl_info = FlagsEnum(Byte, req_inc_idle_current=1, support_uicc_suspend=2) addl_info = FlagsEnum(Byte, req_inc_idle_current=1,
self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info) support_uicc_suspend=2)
self._construct = Struct(
'max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info)
class CardProfileUICC(CardProfile): class CardProfileUICC(CardProfile):
ORDER = 1 ORDER = 1
def __init__(self, name = 'UICC'): def __init__(self, name='UICC'):
files = [ files = [
EF_DIR(), EF_DIR(),
EF_ICCID(), EF_ICCID(),
@@ -714,10 +753,11 @@ class CardProfileUICC(CardProfile):
}, },
} }
super().__init__(name, desc='ETSI TS 102 221', cla="00", sel_ctrl="0004", files_in_mf=files, sw=sw) super().__init__(name, desc='ETSI TS 102 221', cla="00",
sel_ctrl="0004", files_in_mf=files, sw=sw)
@staticmethod @staticmethod
def decode_select_response(resp_hex:str) -> object: def decode_select_response(resp_hex: str) -> object:
"""ETSI TS 102 221 Section 11.1.1.3""" """ETSI TS 102 221 Section 11.1.1.3"""
fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP) fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP)
resp_hex = resp_hex.upper() resp_hex = resp_hex.upper()
@@ -738,9 +778,10 @@ class CardProfileUICC(CardProfile):
return tlv_key_replace(FCP_TLV_MAP, r) return tlv_key_replace(FCP_TLV_MAP, r)
@staticmethod @staticmethod
def match_with_card(scc:SimCardCommands) -> bool: def match_with_card(scc: SimCardCommands) -> bool:
return match_uicc(scc) return match_uicc(scc)
class CardProfileUICCSIM(CardProfileUICC): class CardProfileUICCSIM(CardProfileUICC):
"""Same as above, but including 2G SIM support""" """Same as above, but including 2G SIM support"""
@@ -754,5 +795,5 @@ class CardProfileUICCSIM(CardProfileUICC):
self.files_in_mf.append(DF_GSM()) self.files_in_mf.append(DF_GSM())
@staticmethod @staticmethod
def match_with_card(scc:SimCardCommands) -> bool: def match_with_card(scc: SimCardCommands) -> bool:
return match_uicc(scc) and match_sim(scc) return match_uicc(scc) and match_sim(scc)

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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