From c91085e744fa5fd136e73117e720af86c8418a2e Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Thu, 10 Feb 2022 18:05:45 +0100 Subject: [PATCH] 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 --- pySim-prog.py | 1227 +++++++-------- pySim-read.py | 546 +++---- pySim-shell.py | 1359 +++++++++-------- pySim/__init__.py | 1 - pySim/ara_m.py | 82 +- pySim/card_handler.py | 172 +-- pySim/card_key_provider.py | 222 +-- pySim/cards.py | 2574 ++++++++++++++++---------------- pySim/cat.py | 270 ++-- pySim/commands.py | 1120 +++++++------- pySim/construct.py | 24 +- pySim/exceptions.py | 54 +- pySim/filesystem.py | 334 +++-- pySim/gsm_r.py | 105 +- pySim/iso7816_4.py | 20 +- pySim/jsonpath.py | 3 +- pySim/profile.py | 200 +-- pySim/sysmocom_sja2.py | 101 +- pySim/tlv.py | 56 +- pySim/transport/__init__.py | 369 ++--- pySim/transport/calypso.py | 183 +-- pySim/transport/modem_atcmd.py | 224 +-- pySim/transport/pcsc.py | 95 +- pySim/transport/serial.py | 344 ++--- pySim/ts_102_221.py | 233 +-- pySim/ts_31_102.py | 1132 ++++++++------ pySim/ts_31_103.py | 120 +- pySim/ts_51_011.py | 1048 +++++++------ pySim/utils.py | 1832 ++++++++++++----------- 29 files changed, 7501 insertions(+), 6549 deletions(-) diff --git a/pySim-prog.py b/pySim-prog.py index 334431e0..ade213b6 100755 --- a/pySim-prog.py +++ b/pySim-prog.py @@ -41,740 +41,759 @@ from pySim.ts_51_011 import EF, EF_AD from pySim.card_handler import * from pySim.utils import * + def parse_options(): - parser = OptionParser(usage="usage: %prog [options]") + parser = OptionParser(usage="usage: %prog [options]") - parser.add_option("-d", "--device", dest="device", metavar="DEV", - help="Serial Device for SIM access [default: %default]", - default="/dev/ttyUSB0", - ) - parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD", - help="Baudrate used for SIM access [default: %default]", - default=9600, - ) - parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC", - help="Which PC/SC reader number for SIM access", - default=None, - ) - parser.add_option("--modem-device", dest="modem_dev", metavar="DEV", - help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)", - default=None, - ) - parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD", - help="Baudrate used for modem's port [default: %default]", - default=115200, - ) - parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH", - help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)", - default=None, - ) - parser.add_option("-t", "--type", dest="type", - help="Card type (user -t list to view) [default: %default]", - default="auto", - ) - parser.add_option("-T", "--probe", dest="probe", - help="Determine card type", - default=False, action="store_true" - ) - parser.add_option("-a", "--pin-adm", dest="pin_adm", - help="ADM PIN used for provisioning (overwrites default)", - ) - parser.add_option("-A", "--pin-adm-hex", dest="pin_adm_hex", - help="ADM PIN used for provisioning, as hex string (16 characters long", - ) - parser.add_option("-e", "--erase", dest="erase", action='store_true', - help="Erase beforehand [default: %default]", - default=False, - ) + parser.add_option("-d", "--device", dest="device", metavar="DEV", + help="Serial Device for SIM access [default: %default]", + default="/dev/ttyUSB0", + ) + parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD", + help="Baudrate used for SIM access [default: %default]", + default=9600, + ) + parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC", + help="Which PC/SC reader number for SIM access", + default=None, + ) + parser.add_option("--modem-device", dest="modem_dev", metavar="DEV", + help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)", + default=None, + ) + parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD", + help="Baudrate used for modem's port [default: %default]", + default=115200, + ) + parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH", + help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)", + default=None, + ) + parser.add_option("-t", "--type", dest="type", + help="Card type (user -t list to view) [default: %default]", + default="auto", + ) + parser.add_option("-T", "--probe", dest="probe", + help="Determine card type", + default=False, action="store_true" + ) + parser.add_option("-a", "--pin-adm", dest="pin_adm", + help="ADM PIN used for provisioning (overwrites default)", + ) + parser.add_option("-A", "--pin-adm-hex", dest="pin_adm_hex", + help="ADM PIN used for provisioning, as hex string (16 characters long", + ) + parser.add_option("-e", "--erase", dest="erase", action='store_true', + help="Erase beforehand [default: %default]", + default=False, + ) - parser.add_option("-S", "--source", dest="source", - help="Data Source[default: %default]", - default="cmdline", - ) + parser.add_option("-S", "--source", dest="source", + help="Data Source[default: %default]", + default="cmdline", + ) - # if mode is "cmdline" - parser.add_option("-n", "--name", dest="name", - help="Operator name [default: %default]", - default="Magic", - ) - parser.add_option("-c", "--country", dest="country", type="int", metavar="CC", - help="Country code [default: %default]", - default=1, - ) - parser.add_option("-x", "--mcc", dest="mcc", type="string", - help="Mobile Country Code [default: %default]", - default="901", - ) - parser.add_option("-y", "--mnc", dest="mnc", type="string", - help="Mobile Network Code [default: %default]", - default="55", - ) - parser.add_option("--mnclen", dest="mnclen", type="choice", - help="Length of Mobile Network Code [default: %default]", - default=2, - choices=[2, 3], - ) - parser.add_option("-m", "--smsc", dest="smsc", - help="SMSC number (Start with + for international no.) [default: '00 + country code + 5555']", - ) - parser.add_option("-M", "--smsp", dest="smsp", - help="Raw SMSP content in hex [default: auto from SMSC]", - ) + # if mode is "cmdline" + parser.add_option("-n", "--name", dest="name", + help="Operator name [default: %default]", + default="Magic", + ) + parser.add_option("-c", "--country", dest="country", type="int", metavar="CC", + help="Country code [default: %default]", + default=1, + ) + parser.add_option("-x", "--mcc", dest="mcc", type="string", + help="Mobile Country Code [default: %default]", + default="901", + ) + parser.add_option("-y", "--mnc", dest="mnc", type="string", + help="Mobile Network Code [default: %default]", + default="55", + ) + parser.add_option("--mnclen", dest="mnclen", type="choice", + help="Length of Mobile Network Code [default: %default]", + default=2, + choices=[2, 3], + ) + parser.add_option("-m", "--smsc", dest="smsc", + help="SMSC number (Start with + for international no.) [default: '00 + country code + 5555']", + ) + parser.add_option("-M", "--smsp", dest="smsp", + help="Raw SMSP content in hex [default: auto from SMSC]", + ) - parser.add_option("-s", "--iccid", dest="iccid", metavar="ID", - help="Integrated Circuit Card ID", - ) - parser.add_option("-i", "--imsi", dest="imsi", - help="International Mobile Subscriber Identity", - ) - parser.add_option("--msisdn", dest="msisdn", - help="Mobile Subscriber Integrated Services Digital Number", - ) - parser.add_option("-k", "--ki", dest="ki", - help="Ki (default is to randomize)", - ) - parser.add_option("-o", "--opc", dest="opc", - help="OPC (default is to randomize)", - ) - parser.add_option("--op", dest="op", - help="Set OP to derive OPC from OP and KI", - ) - parser.add_option("--acc", dest="acc", - help="Set ACC bits (Access Control Code). not all card types are supported", - ) - parser.add_option("--opmode", dest="opmode", type="choice", - help="Set UE Operation Mode in EF.AD (Administrative Data)", - default=None, - choices=['{:02X}'.format(int(m)) for m in EF_AD.OP_MODE], - ) - parser.add_option("--epdgid", dest="epdgid", - help="Set Home Evolved Packet Data Gateway (ePDG) Identifier. (Only FQDN format supported)", - ) - parser.add_option("--epdgSelection", dest="epdgSelection", - help="Set PLMN for ePDG Selection Information. (Only Operator Identifier FQDN format supported)", - ) - parser.add_option("--pcscf", dest="pcscf", - help="Set Proxy Call Session Control Function (P-CSCF) Address. (Only FQDN format supported)", - ) - parser.add_option("--ims-hdomain", dest="ims_hdomain", - help="Set IMS Home Network Domain Name in FQDN format", - ) - parser.add_option("--impi", dest="impi", - help="Set IMS private user identity", - ) - parser.add_option("--impu", dest="impu", - help="Set IMS public user identity", - ) - parser.add_option("--read-imsi", dest="read_imsi", action="store_true", - help="Read the IMSI from the CARD", default=False - ) - parser.add_option("--read-iccid", dest="read_iccid", action="store_true", - help="Read the ICCID from the CARD", default=False - ) - parser.add_option("-z", "--secret", dest="secret", metavar="STR", - help="Secret used for ICCID/IMSI autogen", - ) - parser.add_option("-j", "--num", dest="num", type=int, - help="Card # used for ICCID/IMSI autogen", - ) - parser.add_option("--batch", dest="batch_mode", - help="Enable batch mode [default: %default]", - default=False, action='store_true', - ) - parser.add_option("--batch-state", dest="batch_state", metavar="FILE", - help="Optional batch state file", - ) + parser.add_option("-s", "--iccid", dest="iccid", metavar="ID", + help="Integrated Circuit Card ID", + ) + parser.add_option("-i", "--imsi", dest="imsi", + help="International Mobile Subscriber Identity", + ) + parser.add_option("--msisdn", dest="msisdn", + help="Mobile Subscriber Integrated Services Digital Number", + ) + parser.add_option("-k", "--ki", dest="ki", + help="Ki (default is to randomize)", + ) + parser.add_option("-o", "--opc", dest="opc", + help="OPC (default is to randomize)", + ) + parser.add_option("--op", dest="op", + help="Set OP to derive OPC from OP and KI", + ) + parser.add_option("--acc", dest="acc", + help="Set ACC bits (Access Control Code). not all card types are supported", + ) + parser.add_option("--opmode", dest="opmode", type="choice", + help="Set UE Operation Mode in EF.AD (Administrative Data)", + default=None, + choices=['{:02X}'.format(int(m)) for m in EF_AD.OP_MODE], + ) + parser.add_option("--epdgid", dest="epdgid", + help="Set Home Evolved Packet Data Gateway (ePDG) Identifier. (Only FQDN format supported)", + ) + parser.add_option("--epdgSelection", dest="epdgSelection", + help="Set PLMN for ePDG Selection Information. (Only Operator Identifier FQDN format supported)", + ) + parser.add_option("--pcscf", dest="pcscf", + help="Set Proxy Call Session Control Function (P-CSCF) Address. (Only FQDN format supported)", + ) + parser.add_option("--ims-hdomain", dest="ims_hdomain", + help="Set IMS Home Network Domain Name in FQDN format", + ) + parser.add_option("--impi", dest="impi", + help="Set IMS private user identity", + ) + parser.add_option("--impu", dest="impu", + help="Set IMS public user identity", + ) + parser.add_option("--read-imsi", dest="read_imsi", action="store_true", + help="Read the IMSI from the CARD", default=False + ) + parser.add_option("--read-iccid", dest="read_iccid", action="store_true", + help="Read the ICCID from the CARD", default=False + ) + parser.add_option("-z", "--secret", dest="secret", metavar="STR", + help="Secret used for ICCID/IMSI autogen", + ) + parser.add_option("-j", "--num", dest="num", type=int, + help="Card # used for ICCID/IMSI autogen", + ) + parser.add_option("--batch", dest="batch_mode", + help="Enable batch mode [default: %default]", + default=False, action='store_true', + ) + parser.add_option("--batch-state", dest="batch_state", metavar="FILE", + help="Optional batch state file", + ) - # if mode is "csv" - parser.add_option("--read-csv", dest="read_csv", metavar="FILE", - help="Read parameters from CSV file rather than command line") + # if mode is "csv" + parser.add_option("--read-csv", dest="read_csv", metavar="FILE", + help="Read parameters from CSV file rather than command line") + parser.add_option("--write-csv", dest="write_csv", metavar="FILE", + help="Append generated parameters in CSV file", + ) + parser.add_option("--write-hlr", dest="write_hlr", metavar="FILE", + help="Append generated parameters to OpenBSC HLR sqlite3", + ) + parser.add_option("--dry-run", dest="dry_run", + help="Perform a 'dry run', don't actually program the card", + default=False, action="store_true") + parser.add_option("--card_handler", dest="card_handler_config", metavar="FILE", + help="Use automatic card handling machine") - parser.add_option("--write-csv", dest="write_csv", metavar="FILE", - help="Append generated parameters in CSV file", - ) - parser.add_option("--write-hlr", dest="write_hlr", metavar="FILE", - help="Append generated parameters to OpenBSC HLR sqlite3", - ) - parser.add_option("--dry-run", dest="dry_run", - help="Perform a 'dry run', don't actually program the card", - default=False, action="store_true") - parser.add_option("--card_handler", dest="card_handler_config", metavar="FILE", - help="Use automatic card handling machine") + (options, args) = parser.parse_args() - (options, args) = parser.parse_args() + if options.type == 'list': + for kls in _cards_classes: + print(kls.name) + sys.exit(0) - if options.type == 'list': - for kls in _cards_classes: - print(kls.name) - sys.exit(0) + if options.probe: + return options - if options.probe: - return options + if options.source == 'csv': + if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False): + parser.error( + "CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode") + if options.read_csv is None: + parser.error("CSV mode requires a CSV input file") + elif options.source == 'cmdline': + if ((options.imsi is None) or (options.iccid is None)) and (options.num is None): + parser.error( + "If either IMSI or ICCID isn't specified, num is required") + else: + parser.error("Only `cmdline' and `csv' sources supported") - if options.source == 'csv': - if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False): - parser.error("CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode") - if options.read_csv is None: - parser.error("CSV mode requires a CSV input file") - elif options.source == 'cmdline': - if ((options.imsi is None) or (options.iccid is None)) and (options.num is None): - parser.error("If either IMSI or ICCID isn't specified, num is required") - else: - parser.error("Only `cmdline' and `csv' sources supported") + if (options.read_csv is not None) and (options.source != 'csv'): + parser.error("You cannot specify a CSV input file in source != csv") - if (options.read_csv is not None) and (options.source != 'csv'): - parser.error("You cannot specify a CSV input file in source != csv") + if (options.batch_mode) and (options.num is None): + options.num = 0 - if (options.batch_mode) and (options.num is None): - options.num = 0 + if (options.batch_mode): + if (options.imsi is not None) or (options.iccid is not None): + parser.error( + "Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more information") - if (options.batch_mode): - if (options.imsi is not None) or (options.iccid is not None): - parser.error("Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more informations") + if args: + parser.error("Extraneous arguments") - if args: - parser.error("Extraneous arguments") - - return options + return options def _digits(secret, usage, len, num): - seed = secret + usage + '%d' % num - s = hashlib.sha1(seed.encode()) - d = ''.join(['%02d' % x for x in s.digest()]) - return d[0:len] + seed = secret + usage + '%d' % num + s = hashlib.sha1(seed.encode()) + d = ''.join(['%02d' % x for x in s.digest()]) + return d[0:len] + def _mcc_mnc_digits(mcc, mnc): - return '%s%s' % (mcc, mnc) + return '%s%s' % (mcc, mnc) + 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): - 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): - hc = '0123456789abcdef' - return all([x in hc for x in s.lower()]) and ((l== -1) or (len(s) == l)) + hc = '0123456789abcdef' + return all([x in hc for x in s.lower()]) and ((l == -1) or (len(s) == l)) def _dbi_binary_quote(s): - # Count usage of each char - cnt = {} - for c in s: - cnt[c] = cnt.get(c, 0) + 1 + # Count usage of each char + cnt = {} + for c in s: + cnt[c] = cnt.get(c, 0) + 1 - # Find best offset - e = 0 - m = len(s) - for i in range(1, 256): - if i == 39: - continue - sum_ = cnt.get(i, 0) + cnt.get((i+1)&0xff, 0) + cnt.get((i+39)&0xff, 0) - if sum_ < m: - m = sum_ - e = i - if m == 0: # No overhead ? use this ! - break + # Find best offset + e = 0 + m = len(s) + for i in range(1, 256): + if i == 39: + continue + sum_ = cnt.get(i, 0) + cnt.get((i+1) & 0xff, 0) + \ + cnt.get((i+39) & 0xff, 0) + if sum_ < m: + m = sum_ + e = i + if m == 0: # No overhead ? use this ! + break - # Generate output - out = [] - out.append( chr(e) ) # Offset - for c in s: - x = (256 + ord(c) - e) % 256 - if x in (0, 1, 39): - out.append('\x01') - out.append(chr(x+1)) - else: - out.append(chr(x)) + # Generate output + out = [] + out.append(chr(e)) # Offset + for c in s: + x = (256 + ord(c) - e) % 256 + if x in (0, 1, 39): + out.append('\x01') + out.append(chr(x+1)) + else: + out.append(chr(x)) + + return ''.join(out) - return ''.join(out) def gen_parameters(opts): - """Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the - options given by the user""" + """Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the + options given by the user""" - # MCC/MNC - mcc = opts.mcc - mnc = opts.mnc + # MCC/MNC + mcc = opts.mcc + mnc = opts.mnc - if not mcc.isdigit() or not mnc.isdigit(): - raise ValueError('mcc & mnc must only contain decimal digits') - if len(mcc) < 1 or len(mcc) > 3: - raise ValueError('mcc must be between 1 .. 3 digits') - if len(mnc) < 1 or len(mnc) > 3: - raise ValueError('mnc must be between 1 .. 3 digits') + if not mcc.isdigit() or not mnc.isdigit(): + raise ValueError('mcc & mnc must only contain decimal digits') + if len(mcc) < 1 or len(mcc) > 3: + raise ValueError('mcc must be between 1 .. 3 digits') + if len(mnc) < 1 or len(mnc) > 3: + raise ValueError('mnc must be between 1 .. 3 digits') - # MCC always has 3 digits - mcc = lpad(mcc, 3, "0") - # MNC must be at least 2 digits - mnc = lpad(mnc, 2, "0") + # MCC always has 3 digits + mcc = lpad(mcc, 3, "0") + # MNC must be at least 2 digits + mnc = lpad(mnc, 2, "0") - # Digitize country code (2 or 3 digits) - cc_digits = _cc_digits(opts.country) + # Digitize country code (2 or 3 digits) + cc_digits = _cc_digits(opts.country) - # Digitize MCC/MNC (5 or 6 digits) - plmn_digits = _mcc_mnc_digits(mcc, mnc) + # Digitize MCC/MNC (5 or 6 digits) + plmn_digits = _mcc_mnc_digits(mcc, mnc) - if opts.name is not None: - if len(opts.name) > 16: - raise ValueError('Service Provider Name must max 16 characters!') + if opts.name is not None: + if len(opts.name) > 16: + raise ValueError('Service Provider Name must max 16 characters!') - if opts.msisdn is not None: - msisdn = opts.msisdn - if msisdn[0] == '+': - msisdn = msisdn[1:] - if not msisdn.isdigit(): - raise ValueError('MSISDN must be digits only! ' - 'Start with \'+\' for international numbers.') - if len(msisdn) > 10 * 2: - # TODO: Support MSISDN of length > 20 (10 Bytes) - raise ValueError('MSISDNs longer than 20 digits are not (yet) supported.') + if opts.msisdn is not None: + msisdn = opts.msisdn + if msisdn[0] == '+': + msisdn = msisdn[1:] + if not msisdn.isdigit(): + raise ValueError('MSISDN must be digits only! ' + 'Start with \'+\' for international numbers.') + if len(msisdn) > 10 * 2: + # TODO: Support MSISDN of length > 20 (10 Bytes) + raise ValueError( + 'MSISDNs longer than 20 digits are not (yet) supported.') - # ICCID (19 digits, E.118), though some phase1 vendors use 20 :( - if opts.iccid is not None: - iccid = opts.iccid - if not _isnum(iccid, 19) and not _isnum(iccid, 20): - raise ValueError('ICCID must be 19 or 20 digits !') + # ICCID (19 digits, E.118), though some phase1 vendors use 20 :( + if opts.iccid is not None: + iccid = opts.iccid + if not _isnum(iccid, 19) and not _isnum(iccid, 20): + raise ValueError('ICCID must be 19 or 20 digits !') - else: - if opts.num is None: - raise ValueError('Neither ICCID nor card number specified !') + else: + if opts.num is None: + raise ValueError('Neither ICCID nor card number specified !') - iccid = ( - '89' + # Common prefix (telecom) - cc_digits + # Country Code on 2/3 digits - plmn_digits # MCC/MNC on 5/6 digits - ) + iccid = ( + '89' + # Common prefix (telecom) + cc_digits + # Country Code on 2/3 digits + plmn_digits # MCC/MNC on 5/6 digits + ) - ml = 18 - len(iccid) + ml = 18 - len(iccid) - if opts.secret is None: - # The raw number - iccid += ('%%0%dd' % ml) % opts.num - else: - # Randomized digits - iccid += _digits(opts.secret, 'ccid', ml, opts.num) + if opts.secret is None: + # The raw number + iccid += ('%%0%dd' % ml) % opts.num + else: + # Randomized digits + iccid += _digits(opts.secret, 'ccid', ml, opts.num) - # Add checksum digit - iccid += ('%1d' % calculate_luhn(iccid)) + # Add checksum digit + iccid += ('%1d' % calculate_luhn(iccid)) - # IMSI (15 digits usually) - if opts.imsi is not None: - imsi = opts.imsi - if not _isnum(imsi): - raise ValueError('IMSI must be digits only !') + # IMSI (15 digits usually) + if opts.imsi is not None: + imsi = opts.imsi + if not _isnum(imsi): + raise ValueError('IMSI must be digits only !') - else: - if opts.num is None: - raise ValueError('Neither IMSI nor card number specified !') + else: + if opts.num is None: + raise ValueError('Neither IMSI nor card number specified !') - ml = 15 - len(plmn_digits) + ml = 15 - len(plmn_digits) - if opts.secret is None: - # The raw number - msin = ('%%0%dd' % ml) % opts.num - else: - # Randomized digits - msin = _digits(opts.secret, 'imsi', ml, opts.num) + if opts.secret is None: + # The raw number + msin = ('%%0%dd' % ml) % opts.num + else: + # Randomized digits + msin = _digits(opts.secret, 'imsi', ml, opts.num) - imsi = ( - plmn_digits + # MCC/MNC on 5/6 digits - msin # MSIN - ) + imsi = ( + plmn_digits + # MCC/MNC on 5/6 digits + msin # MSIN + ) - # SMSP - if opts.smsp is not None: - smsp = opts.smsp - if not _ishex(smsp): - raise ValueError('SMSP must be hex digits only !') - if len(smsp) < 28*2: - raise ValueError('SMSP must be at least 28 bytes') + # SMSP + if opts.smsp is not None: + smsp = opts.smsp + if not _ishex(smsp): + raise ValueError('SMSP must be hex digits only !') + if len(smsp) < 28*2: + raise ValueError('SMSP must be at least 28 bytes') - else: - ton = "81" - if opts.smsc is not None: - smsc = opts.smsc - if smsc[0] == '+': - ton = "91" - smsc = smsc[1:] - if not _isnum(smsc): - raise ValueError('SMSC must be digits only!\n \ + else: + ton = "81" + if opts.smsc is not None: + smsc = opts.smsc + if smsc[0] == '+': + ton = "91" + smsc = smsc[1:] + if not _isnum(smsc): + raise ValueError('SMSC must be digits only!\n \ Start with \'+\' for international numbers') - else: - smsc = '00%d' % opts.country + '5555' # Hack ... + else: + smsc = '00%d' % opts.country + '5555' # Hack ... - smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + swap_nibbles(rpad(smsc, 20)) + smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + \ + swap_nibbles(rpad(smsc, 20)) - smsp = ( - 'e1' + # Parameters indicator - 'ff' * 12 + # TP-Destination address - smsc + # TP-Service Centre Address - '00' + # TP-Protocol identifier - '00' + # TP-Data coding scheme - '00' # TP-Validity period - ) + smsp = ( + 'e1' + # Parameters indicator + 'ff' * 12 + # TP-Destination address + smsc + # TP-Service Centre Address + '00' + # TP-Protocol identifier + '00' + # TP-Data coding scheme + '00' # TP-Validity period + ) - # ACC - if opts.acc is not None: - acc = opts.acc - if not _ishex(acc): - raise ValueError('ACC must be hex digits only !') - if len(acc) != 2*2: - raise ValueError('ACC must be exactly 2 bytes') + # ACC + if opts.acc is not None: + acc = opts.acc + if not _ishex(acc): + raise ValueError('ACC must be hex digits only !') + if len(acc) != 2*2: + raise ValueError('ACC must be exactly 2 bytes') - else: - acc = None + else: + acc = None - # Ki (random) - if opts.ki is not None: - ki = opts.ki - if not re.match('^[0-9a-fA-F]{32}$', ki): - raise ValueError('Ki needs to be 128 bits, in hex format') - else: - ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]) + # Ki (random) + if opts.ki is not None: + ki = opts.ki + if not re.match('^[0-9a-fA-F]{32}$', ki): + raise ValueError('Ki needs to be 128 bits, in hex format') + else: + ki = ''.join(['%02x' % random.randrange(0, 256) for i in range(16)]) - # OPC (random) - if opts.opc is not None: - opc = opts.opc - if not re.match('^[0-9a-fA-F]{32}$', opc): - raise ValueError('OPC needs to be 128 bits, in hex format') + # OPC (random) + if opts.opc is not None: + opc = opts.opc + if not re.match('^[0-9a-fA-F]{32}$', opc): + raise ValueError('OPC needs to be 128 bits, in hex format') - elif opts.op is not None: - opc = derive_milenage_opc(ki, opts.op) - else: - opc = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]) + elif opts.op is not None: + opc = derive_milenage_opc(ki, opts.op) + else: + 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) - # ePDG Selection Information - if opts.epdgSelection: - if len(opts.epdgSelection) < 5 or len(opts.epdgSelection) > 6: - raise ValueError('ePDG Selection Information is not valid') - epdg_mcc = opts.epdgSelection[:3] - epdg_mnc = opts.epdgSelection[3:] - if not epdg_mcc.isdigit() or not epdg_mnc.isdigit(): - raise ValueError('PLMN for ePDG Selection must only contain decimal digits') + # ePDG Selection Information + if opts.epdgSelection: + if len(opts.epdgSelection) < 5 or len(opts.epdgSelection) > 6: + raise ValueError('ePDG Selection Information is not valid') + epdg_mcc = opts.epdgSelection[:3] + epdg_mnc = opts.epdgSelection[3:] + if not epdg_mcc.isdigit() or not epdg_mnc.isdigit(): + raise ValueError( + 'PLMN for ePDG Selection must only contain decimal digits') - # Return that - return { - 'name' : opts.name, - 'iccid' : iccid, - 'mcc' : mcc, - 'mnc' : mnc, - 'imsi' : imsi, - 'smsp' : smsp, - 'ki' : ki, - 'opc' : opc, - 'acc' : acc, - 'pin_adm' : pin_adm, - 'msisdn' : opts.msisdn, - 'epdgid' : opts.epdgid, - 'epdgSelection' : opts.epdgSelection, - 'pcscf' : opts.pcscf, - 'ims_hdomain': opts.ims_hdomain, - 'impi' : opts.impi, - 'impu' : opts.impu, - 'opmode': opts.opmode, - } + # Return that + return { + 'name': opts.name, + 'iccid': iccid, + 'mcc': mcc, + 'mnc': mnc, + 'imsi': imsi, + 'smsp': smsp, + 'ki': ki, + 'opc': opc, + 'acc': acc, + 'pin_adm': pin_adm, + 'msisdn': opts.msisdn, + 'epdgid': opts.epdgid, + 'epdgSelection': opts.epdgSelection, + 'pcscf': opts.pcscf, + 'ims_hdomain': opts.ims_hdomain, + 'impi': opts.impi, + 'impu': opts.impu, + 'opmode': opts.opmode, + } def print_parameters(params): - s = ["Generated card parameters :"] - if 'name' in params: - s.append(" > Name : %(name)s") - if 'smsp' in params: - s.append(" > SMSP : %(smsp)s") - s.append(" > ICCID : %(iccid)s") - s.append(" > MCC/MNC : %(mcc)s/%(mnc)s") - s.append(" > IMSI : %(imsi)s") - s.append(" > Ki : %(ki)s") - s.append(" > OPC : %(opc)s") - if 'acc' in params: - s.append(" > ACC : %(acc)s") - s.append(" > ADM1(hex): %(pin_adm)s") - if 'opmode' in params: - s.append(" > OPMODE : %(opmode)s") - print("\n".join(s) % params) + s = ["Generated card parameters :"] + if 'name' in params: + s.append(" > Name : %(name)s") + if 'smsp' in params: + s.append(" > SMSP : %(smsp)s") + s.append(" > ICCID : %(iccid)s") + s.append(" > MCC/MNC : %(mcc)s/%(mnc)s") + s.append(" > IMSI : %(imsi)s") + s.append(" > Ki : %(ki)s") + s.append(" > OPC : %(opc)s") + if 'acc' in params: + s.append(" > ACC : %(acc)s") + s.append(" > ADM1(hex): %(pin_adm)s") + if 'opmode' in params: + s.append(" > OPMODE : %(opmode)s") + print("\n".join(s) % params) def write_params_csv(opts, params): - # csv - if opts.write_csv: - import csv - row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki', 'opc'] - f = open(opts.write_csv, 'a') - cw = csv.writer(f) - cw.writerow([params[x] for x in row]) - f.close() + # csv + if opts.write_csv: + import csv + row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki', 'opc'] + f = open(opts.write_csv, 'a') + cw = csv.writer(f) + cw.writerow([params[x] for x in row]) + f.close() + def _read_params_csv(opts, iccid=None, imsi=None): - import csv - f = open(opts.read_csv, 'r') - cr = csv.DictReader(f) + import csv + f = open(opts.read_csv, 'r') + cr = csv.DictReader(f) - # Lower-case fieldnames - cr.fieldnames = [ field.lower() for field in cr.fieldnames ] + # Lower-case fieldnames + cr.fieldnames = [field.lower() for field in cr.fieldnames] - i = 0 - if not 'iccid' in cr.fieldnames: - raise Exception("CSV file in wrong format!") - for row in cr: - if opts.num is not None and opts.read_iccid is False and opts.read_imsi is False: - if opts.num == i: - f.close() - return row - i += 1 - if row['iccid'] == iccid: - f.close() - return row + i = 0 + if not 'iccid' in cr.fieldnames: + raise Exception("CSV file in wrong format!") + for row in cr: + if opts.num is not None and opts.read_iccid is False and opts.read_imsi is False: + if opts.num == i: + f.close() + return row + i += 1 + if row['iccid'] == iccid: + f.close() + return row - if row['imsi'] == imsi: - f.close() - return row + if row['imsi'] == imsi: + f.close() + return row + + f.close() + return None - f.close() - return None def read_params_csv(opts, imsi=None, iccid=None): - row = _read_params_csv(opts, iccid=iccid, imsi=imsi) - if row is not None: - row['mcc'] = row.get('mcc', mcc_from_imsi(row.get('imsi'))) - row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'))) + row = _read_params_csv(opts, iccid=iccid, imsi=imsi) + if row is not None: + row['mcc'] = row.get('mcc', mcc_from_imsi(row.get('imsi'))) + row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'))) - pin_adm = None - # We need to escape the pin_adm we get from the csv - if 'pin_adm' in row: - pin_adm = ''.join(['%02x'%(ord(x)) for x in row['pin_adm']]) - # Stay compatible to the odoo csv format - elif 'adm1' in row: - pin_adm = ''.join(['%02x'%(ord(x)) for x in row['adm1']]) - if pin_adm: - row['pin_adm'] = rpad(pin_adm, 16) + pin_adm = None + # We need to escape the pin_adm we get from the csv + if 'pin_adm' in row: + pin_adm = ''.join(['%02x' % (ord(x)) for x in row['pin_adm']]) + # Stay compatible to the odoo csv format + elif 'adm1' in row: + pin_adm = ''.join(['%02x' % (ord(x)) for x in row['adm1']]) + if pin_adm: + row['pin_adm'] = rpad(pin_adm, 16) - # If the CSV-File defines a pin_adm_hex field use this field to - # generate pin_adm from that. - pin_adm_hex = row.get('pin_adm_hex') - if pin_adm_hex: - if len(pin_adm_hex) == 16: - row['pin_adm'] = pin_adm_hex - # Ensure that it's hex-encoded - try: - try_encode = h2b(pin_adm) - except ValueError: - raise ValueError("pin_adm_hex needs to be hex encoded using this option") - else: - raise ValueError("pin_adm_hex needs to be exactly 16 digits (hex encoded)") + # If the CSV-File defines a pin_adm_hex field use this field to + # generate pin_adm from that. + pin_adm_hex = row.get('pin_adm_hex') + if pin_adm_hex: + if len(pin_adm_hex) == 16: + row['pin_adm'] = pin_adm_hex + # Ensure that it's hex-encoded + try: + try_encode = h2b(pin_adm) + except ValueError: + raise ValueError( + "pin_adm_hex needs to be hex encoded using this option") + else: + raise ValueError( + "pin_adm_hex needs to be exactly 16 digits (hex encoded)") - return row + return row def write_params_hlr(opts, params): - # SQLite3 OpenBSC HLR - if opts.write_hlr: - import sqlite3 - conn = sqlite3.connect(opts.write_hlr) + # SQLite3 OpenBSC HLR + if opts.write_hlr: + import sqlite3 + conn = sqlite3.connect(opts.write_hlr) - c = conn.execute( - 'INSERT INTO Subscriber ' + - '(imsi, name, extension, authorized, created, updated) ' + - 'VALUES ' + - '(?,?,?,1,datetime(\'now\'),datetime(\'now\'));', - [ - params['imsi'], - params['name'], - '9' + params['iccid'][-5:-1] - ], - ) - sub_id = c.lastrowid - c.close() + c = conn.execute( + 'INSERT INTO Subscriber ' + + '(imsi, name, extension, authorized, created, updated) ' + + 'VALUES ' + + '(?,?,?,1,datetime(\'now\'),datetime(\'now\'));', + [ + params['imsi'], + params['name'], + '9' + params['iccid'][-5:-1] + ], + ) + sub_id = c.lastrowid + c.close() - c = conn.execute( - 'INSERT INTO AuthKeys ' + - '(subscriber_id, algorithm_id, a3a8_ki)' + - 'VALUES ' + - '(?,?,?)', - [ sub_id, 2, sqlite3.Binary(_dbi_binary_quote(h2b(params['ki']))) ], - ) + c = conn.execute( + 'INSERT INTO AuthKeys ' + + '(subscriber_id, algorithm_id, a3a8_ki)' + + 'VALUES ' + + '(?,?,?)', + [sub_id, 2, sqlite3.Binary( + _dbi_binary_quote(h2b(params['ki'])))], + ) + + conn.commit() + conn.close() - conn.commit() - conn.close() def write_parameters(opts, params): - write_params_csv(opts, params) - write_params_hlr(opts, params) + write_params_csv(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'] + def init_batch(opts): - # Need to do something ? - if not opts.batch_mode: - return + # Need to do something ? + if not opts.batch_mode: + return - for k in BATCH_INCOMPATIBLE: - if getattr(opts, k): - print("Incompatible option with batch_state: %s" % (k,)) - sys.exit(-1) + for k in BATCH_INCOMPATIBLE: + if getattr(opts, k): + print("Incompatible option with batch_state: %s" % (k,)) + sys.exit(-1) - # Don't load state if there is none ... - if not opts.batch_state: - return + # Don't load state if there is none ... + if not opts.batch_state: + return - if not os.path.isfile(opts.batch_state): - print("No state file yet") - return + if not os.path.isfile(opts.batch_state): + print("No state file yet") + return - # Get stored data - fh = open(opts.batch_state) - d = json.loads(fh.read()) - fh.close() + # Get stored data + fh = open(opts.batch_state) + d = json.loads(fh.read()) + fh.close() - for k,v in d.iteritems(): - setattr(opts, k, v) + for k, v in d.iteritems(): + setattr(opts, k, v) def save_batch(opts): - # Need to do something ? - if not opts.batch_mode or not opts.batch_state: - return + # Need to do something ? + if not opts.batch_mode or not opts.batch_state: + return - d = json.dumps(dict([(k,getattr(opts,k)) for k in BATCH_STATE])) - fh = open(opts.batch_state, 'w') - fh.write(d) - fh.close() + d = json.dumps(dict([(k, getattr(opts, k)) for k in BATCH_STATE])) + fh = open(opts.batch_state, 'w') + fh.write(d) + fh.close() def process_card(opts, first, ch): - if opts.dry_run is False: - # Connect transport - ch.get(first) + if opts.dry_run is False: + # Connect transport + ch.get(first) - if opts.dry_run is False: - # Get card - card = card_detect(opts.type, scc) - if card is None: - print("No card detected!") - return -1 + if opts.dry_run is False: + # Get card + card = card_detect(opts.type, scc) + if card is None: + print("No card detected!") + return -1 - # Probe only - if opts.probe: - return 0 + # Probe only + if opts.probe: + return 0 - # Erase if requested - if opts.erase: - print("Formatting ...") - card.erase() - card.reset() + # Erase if requested + if opts.erase: + print("Formatting ...") + card.erase() + card.reset() - # Generate parameters - if opts.source == 'cmdline': - cp = gen_parameters(opts) - elif opts.source == 'csv': - imsi = None - iccid = None - if opts.read_iccid: - if opts.dry_run: - # Connect transport - ch.get(False) - (res,_) = scc.read_binary(['3f00', '2fe2'], length=10) - iccid = dec_iccid(res) - elif opts.read_imsi: - if opts.dry_run: - # Connect transport - ch.get(False) - (res,_) = scc.read_binary(EF['IMSI']) - imsi = swap_nibbles(res)[3:] - else: - imsi = opts.imsi - cp = read_params_csv(opts, imsi=imsi, iccid=iccid) - if cp is None: - print("Error reading parameters from CSV file!\n") - return 2 - print_parameters(cp) + # Generate parameters + if opts.source == 'cmdline': + cp = gen_parameters(opts) + elif opts.source == 'csv': + imsi = None + iccid = None + if opts.read_iccid: + if opts.dry_run: + # Connect transport + ch.get(False) + (res, _) = scc.read_binary(['3f00', '2fe2'], length=10) + iccid = dec_iccid(res) + elif opts.read_imsi: + if opts.dry_run: + # Connect transport + ch.get(False) + (res, _) = scc.read_binary(EF['IMSI']) + imsi = swap_nibbles(res)[3:] + else: + imsi = opts.imsi + cp = read_params_csv(opts, imsi=imsi, iccid=iccid) + if cp is None: + print("Error reading parameters from CSV file!\n") + return 2 + print_parameters(cp) - if opts.dry_run is False: - # Program the card - print("Programming ...") - card.program(cp) - else: - print("Dry Run: NOT PROGRAMMING!") + if opts.dry_run is False: + # Program the card + print("Programming ...") + card.program(cp) + else: + print("Dry Run: NOT PROGRAMMING!") - # Write parameters permanently - write_parameters(opts, cp) + # Write parameters permanently + write_parameters(opts, cp) - # Batch mode state update and save - if opts.num is not None: - opts.num += 1 - save_batch(opts) + # Batch mode state update and save + if opts.num is not None: + opts.num += 1 + save_batch(opts) - ch.done() - return 0 + ch.done() + return 0 if __name__ == '__main__': - # Parse options - opts = parse_options() + # Parse options + opts = parse_options() - # Init card reader driver - sl = init_reader(opts) - if sl is None: - exit(1) + # Init card reader driver + sl = init_reader(opts) + if sl is None: + exit(1) - # Create command layer - scc = SimCardCommands(transport=sl) + # Create command layer + scc = SimCardCommands(transport=sl) - # If we use a CSV file as data input, check if the CSV file exists. - if opts.source == 'csv': - print("Using CSV file as data input: " + str(opts.read_csv)) - if not os.path.isfile(opts.read_csv): - print("CSV file not found!") - sys.exit(1) + # If we use a CSV file as data input, check if the CSV file exists. + if opts.source == 'csv': + print("Using CSV file as data input: " + str(opts.read_csv)) + if not os.path.isfile(opts.read_csv): + print("CSV file not found!") + sys.exit(1) - # Batch mode init - init_batch(opts) + # Batch mode init + init_batch(opts) - if opts.card_handler_config: - ch = CardHandlerAuto(sl, opts.card_handler_config) - else: - ch = CardHandler(sl) + if opts.card_handler_config: + ch = CardHandlerAuto(sl, opts.card_handler_config) + else: + ch = CardHandler(sl) - # Iterate - first = True - card = None + # Iterate + first = True + card = None - while 1: - try: - rc = process_card(opts, first, ch) - except (KeyboardInterrupt): - print("") - print("Terminated by user!") - sys.exit(0) - except (SystemExit): - raise - except: - print("") - print("Card programming failed with an exception:") - print("---------------------8<---------------------") - traceback.print_exc() - print("---------------------8<---------------------") - print("") - rc = -1 + while 1: + try: + rc = process_card(opts, first, ch) + except (KeyboardInterrupt): + print("") + print("Terminated by user!") + sys.exit(0) + except (SystemExit): + raise + except: + print("") + print("Card programming failed with an exception:") + print("---------------------8<---------------------") + traceback.print_exc() + print("---------------------8<---------------------") + print("") + rc = -1 - # Something did not work as well as expected, however, lets - # make sure the card is pulled from the reader. - if rc != 0: - ch.error() + # Something did not work as well as expected, however, lets + # make sure the card is pulled from the reader. + if rc != 0: + ch.error() - # If we are not in batch mode we are done in any case, so lets - # exit here. - if not opts.batch_mode: - sys.exit(rc) + # If we are not in batch mode we are done in any case, so lets + # exit here. + if not opts.batch_mode: + sys.exit(rc) - first = False + first = False diff --git a/pySim-read.py b/pySim-read.py index 5e481167..b7fe1f2b 100755 --- a/pySim-read.py +++ b/pySim-read.py @@ -41,312 +41,322 @@ from pySim.utils import format_xplmn_w_act, dec_st from pySim.utils import h2s, format_ePDGSelection option_parser = argparse.ArgumentParser(prog='pySim-read', - description='Legacy tool for reading some parts of a SIM card', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + description='Legacy tool for reading some parts of a SIM card', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) argparse_add_reader_args(option_parser) -def select_app(adf:str, card:SimCard): - """Select application by its AID""" - sw = 0 - try: - if card._scc.cla_byte == "00": - data, sw = card.select_adf_by_aid(adf) - except SwMatchError as e: - if e.sw_actual == "6a82": - # If we can't select the file because it does not exist, we just remain silent since it means - # that this card just does not have an USIM application installed, which is not an error. - pass - else: - print("ADF." + adf + ": Can't select application -- " + str(e)) - except Exception as e: - print("ADF." + adf + ": Can't select application -- " + str(e)) - return sw +def select_app(adf: str, card: SimCard): + """Select application by its AID""" + sw = 0 + try: + if card._scc.cla_byte == "00": + data, sw = card.select_adf_by_aid(adf) + except SwMatchError as e: + if e.sw_actual == "6a82": + # If we can't select the file because it does not exist, we just remain silent since it means + # that this card just does not have an USIM application installed, which is not an error. + pass + else: + print("ADF." + adf + ": Can't select application -- " + str(e)) + except Exception as e: + print("ADF." + adf + ": Can't select application -- " + str(e)) + + return sw + if __name__ == '__main__': - # Parse options - opts = option_parser.parse_args() + # Parse options + opts = option_parser.parse_args() - # Init card reader driver - sl = init_reader(opts) - if sl is None: - exit(1) + # Init card reader driver + sl = init_reader(opts) + if sl is None: + exit(1) - # Create command layer - scc = SimCardCommands(transport=sl) + # Create command layer + scc = SimCardCommands(transport=sl) - # Wait for SIM card - sl.wait_for_card() + # Wait for SIM card + sl.wait_for_card() - # Assuming UICC SIM - scc.cla_byte = "00" - scc.sel_ctrl = "0004" + # Assuming UICC SIM + scc.cla_byte = "00" + scc.sel_ctrl = "0004" - # Testing for Classic SIM or UICC - (res, sw) = sl.send_apdu(scc.cla_byte + "a4" + scc.sel_ctrl + "02" + "3f00") - if sw == '6e00': - # Just a Classic SIM - scc.cla_byte = "a0" - scc.sel_ctrl = "0000" + # Testing for Classic SIM or UICC + (res, sw) = sl.send_apdu(scc.cla_byte + "a4" + scc.sel_ctrl + "02" + "3f00") + if sw == '6e00': + # Just a Classic SIM + scc.cla_byte = "a0" + scc.sel_ctrl = "0000" - # Read the card - print("Reading ...") + # Read the card + print("Reading ...") - # Initialize Card object by auto detecting the card - card = card_detect("auto", scc) or SimCard(scc) + # Initialize Card object by auto detecting the card + card = card_detect("auto", scc) or SimCard(scc) - # Read all AIDs on the UICC - card.read_aids() + # Read all AIDs on the UICC + card.read_aids() - # EF.ICCID - (res, sw) = card.read_iccid() - if sw == '9000': - print("ICCID: %s" % (res,)) - else: - print("ICCID: Can't read, response code = %s" % (sw,)) + # EF.ICCID + (res, sw) = card.read_iccid() + if sw == '9000': + print("ICCID: %s" % (res,)) + else: + print("ICCID: Can't read, response code = %s" % (sw,)) - # EF.IMSI - (res, sw) = card.read_imsi() - if sw == '9000': - print("IMSI: %s" % (res,)) - else: - print("IMSI: Can't read, response code = %s" % (sw,)) + # EF.IMSI + (res, sw) = card.read_imsi() + if sw == '9000': + print("IMSI: %s" % (res,)) + else: + print("IMSI: Can't read, response code = %s" % (sw,)) - # EF.GID1 - try: - (res, sw) = card.read_gid1() - if sw == '9000': - print("GID1: %s" % (res,)) - else: - print("GID1: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("GID1: Can't read file -- %s" % (str(e),)) + # EF.GID1 + try: + (res, sw) = card.read_gid1() + if sw == '9000': + print("GID1: %s" % (res,)) + else: + print("GID1: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("GID1: Can't read file -- %s" % (str(e),)) - # EF.GID2 - try: - (res, sw) = card.read_binary('GID2') - if sw == '9000': - print("GID2: %s" % (res,)) - else: - print("GID2: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("GID2: Can't read file -- %s" % (str(e),)) + # EF.GID2 + try: + (res, sw) = card.read_binary('GID2') + if sw == '9000': + print("GID2: %s" % (res,)) + else: + print("GID2: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("GID2: Can't read file -- %s" % (str(e),)) - # EF.SMSP - (res, sw) = card.read_record('SMSP', 1) - if sw == '9000': - print("SMSP: %s" % (res,)) - else: - print("SMSP: Can't read, response code = %s" % (sw,)) + # EF.SMSP + (res, sw) = card.read_record('SMSP', 1) + if sw == '9000': + print("SMSP: %s" % (res,)) + else: + print("SMSP: Can't read, response code = %s" % (sw,)) - # EF.SPN - try: - (res, sw) = card.read_spn() - if sw == '9000': - print("SPN: %s" % (res[0] or "Not available")) - print("Show in HPLMN: %s" % (res[1],)) - print("Hide in OPLMN: %s" % (res[2],)) - else: - print("SPN: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("SPN: Can't read file -- %s" % (str(e),)) + # EF.SPN + try: + (res, sw) = card.read_spn() + if sw == '9000': + print("SPN: %s" % (res[0] or "Not available")) + print("Show in HPLMN: %s" % (res[1],)) + print("Hide in OPLMN: %s" % (res[2],)) + else: + print("SPN: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("SPN: Can't read file -- %s" % (str(e),)) - # EF.PLMNsel - try: - (res, sw) = card.read_binary('PLMNsel') - if sw == '9000': - print("PLMNsel: %s" % (res)) - else: - print("PLMNsel: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("PLMNsel: Can't read file -- " + str(e)) + # EF.PLMNsel + try: + (res, sw) = card.read_binary('PLMNsel') + if sw == '9000': + print("PLMNsel: %s" % (res)) + else: + print("PLMNsel: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("PLMNsel: Can't read file -- " + str(e)) - # EF.PLMNwAcT - try: - (res, sw) = card.read_plmn_act() - if sw == '9000': - print("PLMNwAcT:\n%s" % (res)) - else: - print("PLMNwAcT: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("PLMNwAcT: Can't read file -- " + str(e)) + # EF.PLMNwAcT + try: + (res, sw) = card.read_plmn_act() + if sw == '9000': + print("PLMNwAcT:\n%s" % (res)) + else: + print("PLMNwAcT: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("PLMNwAcT: Can't read file -- " + str(e)) - # EF.OPLMNwAcT - try: - (res, sw) = card.read_oplmn_act() - if sw == '9000': - print("OPLMNwAcT:\n%s" % (res)) - else: - print("OPLMNwAcT: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("OPLMNwAcT: Can't read file -- " + str(e)) + # EF.OPLMNwAcT + try: + (res, sw) = card.read_oplmn_act() + if sw == '9000': + print("OPLMNwAcT:\n%s" % (res)) + else: + print("OPLMNwAcT: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("OPLMNwAcT: Can't read file -- " + str(e)) - # EF.HPLMNAcT - try: - (res, sw) = card.read_hplmn_act() - if sw == '9000': - print("HPLMNAcT:\n%s" % (res)) - else: - print("HPLMNAcT: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("HPLMNAcT: Can't read file -- " + str(e)) + # EF.HPLMNAcT + try: + (res, sw) = card.read_hplmn_act() + if sw == '9000': + print("HPLMNAcT:\n%s" % (res)) + else: + print("HPLMNAcT: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("HPLMNAcT: Can't read file -- " + str(e)) - # EF.ACC - (res, sw) = card.read_binary('ACC') - if sw == '9000': - print("ACC: %s" % (res,)) - else: - print("ACC: Can't read, response code = %s" % (sw,)) + # EF.ACC + (res, sw) = card.read_binary('ACC') + if sw == '9000': + print("ACC: %s" % (res,)) + else: + print("ACC: Can't read, response code = %s" % (sw,)) - # EF.MSISDN - try: - (res, sw) = card.read_msisdn() - if sw == '9000': - # (npi, ton, msisdn) = res - if res is not None: - print("MSISDN (NPI=%d ToN=%d): %s" % res) - else: - print("MSISDN: Not available") - else: - print("MSISDN: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("MSISDN: Can't read file -- " + str(e)) + # EF.MSISDN + try: + (res, sw) = card.read_msisdn() + if sw == '9000': + # (npi, ton, msisdn) = res + if res is not None: + print("MSISDN (NPI=%d ToN=%d): %s" % res) + else: + print("MSISDN: Not available") + else: + print("MSISDN: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("MSISDN: Can't read file -- " + str(e)) - # EF.AD - (res, sw) = card.read_binary('AD') - if sw == '9000': - print("Administrative data: %s" % (res,)) - ad = EF_AD() - decoded_data = ad.decode_hex(res) - print("\tMS operation mode: %s" % decoded_data['ms_operation_mode']) - if decoded_data['ofm']: - print("\tCiphering Indicator: enabled") - else: - print("\tCiphering Indicator: disabled") - else: - print("AD: Can't read, response code = %s" % (sw,)) + # EF.AD + (res, sw) = card.read_binary('AD') + if sw == '9000': + print("Administrative data: %s" % (res,)) + ad = EF_AD() + decoded_data = ad.decode_hex(res) + print("\tMS operation mode: %s" % decoded_data['ms_operation_mode']) + if decoded_data['ofm']: + print("\tCiphering Indicator: enabled") + else: + print("\tCiphering Indicator: disabled") + else: + print("AD: Can't read, response code = %s" % (sw,)) - # EF.SST - (res, sw) = card.read_binary('SST') - if sw == '9000': - print("SIM Service Table: %s" % res) - # Print those which are available - print("%s" % dec_st(res)) - else: - print("SIM Service Table: Can't read, response code = %s" % (sw,)) + # EF.SST + (res, sw) = card.read_binary('SST') + if sw == '9000': + print("SIM Service Table: %s" % res) + # Print those which are available + print("%s" % dec_st(res)) + else: + print("SIM Service Table: Can't read, response code = %s" % (sw,)) - # Check whether we have th AID of USIM, if so select it by its AID - # EF.UST - File Id in ADF USIM : 6f38 - sw = select_app("USIM", card) - if sw == '9000': - # Select USIM profile - usim_card = UsimCard(scc) + # Check whether we have th AID of USIM, if so select it by its AID + # EF.UST - File Id in ADF USIM : 6f38 + sw = select_app("USIM", card) + if sw == '9000': + # Select USIM profile + usim_card = UsimCard(scc) - # EF.EHPLMN - if usim_card.file_exists(EF_USIM_ADF_map['EHPLMN']): - (res, sw) = usim_card.read_ehplmn() - if sw == '9000': - print("EHPLMN:\n%s" % (res)) - else: - print("EHPLMN: Can't read, response code = %s" % (sw,)) + # EF.EHPLMN + if usim_card.file_exists(EF_USIM_ADF_map['EHPLMN']): + (res, sw) = usim_card.read_ehplmn() + if sw == '9000': + print("EHPLMN:\n%s" % (res)) + else: + print("EHPLMN: Can't read, response code = %s" % (sw,)) - # EF.UST - try: - if usim_card.file_exists(EF_USIM_ADF_map['UST']): - # res[0] - EF content of UST - # res[1] - Human readable format of services marked available in UST - (res, sw) = usim_card.read_ust() - if sw == '9000': - print("USIM Service Table: %s" % res[0]) - print("%s" % res[1]) - else: - print("USIM Service Table: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("USIM Service Table: Can't read file -- " + str(e)) + # EF.UST + try: + if usim_card.file_exists(EF_USIM_ADF_map['UST']): + # res[0] - EF content of UST + # res[1] - Human readable format of services marked available in UST + (res, sw) = usim_card.read_ust() + if sw == '9000': + print("USIM Service Table: %s" % res[0]) + print("%s" % res[1]) + else: + print("USIM Service Table: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("USIM Service Table: Can't read file -- " + str(e)) - #EF.ePDGId - Home ePDG Identifier - try: - if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']): - (res, sw) = usim_card.read_epdgid() - if sw == '9000': - print("ePDGId:\n%s" % (len(res) and res or '\tNot available\n',)) - else: - print("ePDGId: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("ePDGId: Can't read file -- " + str(e)) + # EF.ePDGId - Home ePDG Identifier + try: + if usim_card.file_exists(EF_USIM_ADF_map['ePDGId']): + (res, sw) = usim_card.read_epdgid() + if sw == '9000': + print("ePDGId:\n%s" % + (len(res) and res or '\tNot available\n',)) + else: + print("ePDGId: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("ePDGId: Can't read file -- " + str(e)) - #EF.ePDGSelection - ePDG Selection Information - try: - if usim_card.file_exists(EF_USIM_ADF_map['ePDGSelection']): - (res, sw) = usim_card.read_ePDGSelection() - if sw == '9000': - print("ePDGSelection:\n%s" % (res,)) - else: - print("ePDGSelection: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("ePDGSelection: Can't read file -- " + str(e)) + # EF.ePDGSelection - ePDG Selection Information + try: + if usim_card.file_exists(EF_USIM_ADF_map['ePDGSelection']): + (res, sw) = usim_card.read_ePDGSelection() + if sw == '9000': + print("ePDGSelection:\n%s" % (res,)) + else: + print("ePDGSelection: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("ePDGSelection: Can't read file -- " + str(e)) - # Select ISIM application by its AID - sw = select_app("ISIM", card) - if sw == '9000': - # Select USIM profile - isim_card = IsimCard(scc) + # Select ISIM application by its AID + sw = select_app("ISIM", card) + if sw == '9000': + # Select USIM profile + isim_card = IsimCard(scc) - #EF.P-CSCF - P-CSCF Address - try: - if isim_card.file_exists(EF_ISIM_ADF_map['PCSCF']): - res = isim_card.read_pcscf() - print("P-CSCF:\n%s" % (len(res) and res or '\tNot available\n',)) - except Exception as e: - print("P-CSCF: Can't read file -- " + str(e)) + # EF.P-CSCF - P-CSCF Address + try: + if isim_card.file_exists(EF_ISIM_ADF_map['PCSCF']): + res = isim_card.read_pcscf() + print("P-CSCF:\n%s" % + (len(res) and res or '\tNot available\n',)) + except Exception as e: + print("P-CSCF: Can't read file -- " + str(e)) - # EF.DOMAIN - Home Network Domain Name e.g. ims.mncXXX.mccXXX.3gppnetwork.org - try: - if isim_card.file_exists(EF_ISIM_ADF_map['DOMAIN']): - (res, sw) = isim_card.read_domain() - if sw == '9000': - print("Home Network Domain Name: %s" % (len(res) and res or 'Not available',)) - else: - print("Home Network Domain Name: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("Home Network Domain Name: Can't read file -- " + str(e)) + # EF.DOMAIN - Home Network Domain Name e.g. ims.mncXXX.mccXXX.3gppnetwork.org + try: + if isim_card.file_exists(EF_ISIM_ADF_map['DOMAIN']): + (res, sw) = isim_card.read_domain() + if sw == '9000': + print("Home Network Domain Name: %s" % + (len(res) and res or 'Not available',)) + else: + print( + "Home Network Domain Name: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("Home Network Domain Name: Can't read file -- " + str(e)) - # EF.IMPI - IMS private user identity - try: - if isim_card.file_exists(EF_ISIM_ADF_map['IMPI']): - (res, sw) = isim_card.read_impi() - if sw == '9000': - print("IMS private user identity: %s" % (len(res) and res or 'Not available',)) - else: - print("IMS private user identity: Can't read, response code = %s" % (sw,)) - except Exception as e: - print("IMS private user identity: Can't read file -- " + str(e)) + # EF.IMPI - IMS private user identity + try: + if isim_card.file_exists(EF_ISIM_ADF_map['IMPI']): + (res, sw) = isim_card.read_impi() + if sw == '9000': + print("IMS private user identity: %s" % + (len(res) and res or 'Not available',)) + else: + print( + "IMS private user identity: Can't read, response code = %s" % (sw,)) + except Exception as e: + print("IMS private user identity: Can't read file -- " + str(e)) - # EF.IMPU - IMS public user identity - try: - if isim_card.file_exists(EF_ISIM_ADF_map['IMPU']): - res = isim_card.read_impu() - print("IMS public user identity:\n%s" % (len(res) and res or '\tNot available\n',)) - except Exception as e: - print("IMS public user identity: Can't read file -- " + str(e)) + # EF.IMPU - IMS public user identity + try: + if isim_card.file_exists(EF_ISIM_ADF_map['IMPU']): + res = isim_card.read_impu() + print("IMS public user identity:\n%s" % + (len(res) and res or '\tNot available\n',)) + except Exception as e: + print("IMS public user identity: Can't read file -- " + str(e)) - # EF.UICCIARI - UICC IARI - try: - if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']): - res = isim_card.read_iari() - print("UICC IARI:\n%s" % (len(res) and res or '\tNot available\n',)) - except Exception as e: - print("UICC IARI: Can't read file -- " + str(e)) + # EF.UICCIARI - UICC IARI + try: + if isim_card.file_exists(EF_ISIM_ADF_map['UICCIARI']): + res = isim_card.read_iari() + print("UICC IARI:\n%s" % + (len(res) and res or '\tNot available\n',)) + except Exception as e: + print("UICC IARI: Can't read file -- " + str(e)) - # EF.IST - (res, sw) = card.read_binary('6f07') - if sw == '9000': - print("ISIM Service Table: %s" % res) - # Print those which are available - print("%s" % dec_st(res, table="isim")) - else: - print("ISIM Service Table: Can't read, response code = %s" % (sw,)) + # EF.IST + (res, sw) = card.read_binary('6f07') + if sw == '9000': + print("ISIM Service Table: %s" % res) + # Print those which are available + print("%s" % dec_st(res, table="isim")) + else: + print("ISIM Service Table: Can't read, response code = %s" % (sw,)) - # Done for this card and maybe for everything ? - print("Done !\n") + # Done for this card and maybe for everything ? + print("Done !\n") diff --git a/pySim-shell.py b/pySim-shell.py index 8e8a1a61..6bbe9ed1 100755 --- a/pySim-shell.py +++ b/pySim-shell.py @@ -61,714 +61,775 @@ import pySim.sysmocom_sja2 from pySim.card_key_provider import CardKeyProviderCsv, card_key_provider_register, card_key_provider_get_field + def init_card(sl): - """ - Detect card in reader and setup card profile and runtime state. This - function must be called at least once on startup. The card and runtime - state object (rs) is required for all pySim-shell commands. - """ + """ + Detect card in reader and setup card profile and runtime state. This + function must be called at least once on startup. The card and runtime + state object (rs) is required for all pySim-shell commands. + """ - # Wait up to three seconds for a card in reader and try to detect - # the card type. - print("Waiting for card...") - try: - sl.wait_for_card(3) - except NoCardError: - print("No card detected!") - return None, None - except: - print("Card not readable!") - return None, None + # Wait up to three seconds for a card in reader and try to detect + # the card type. + print("Waiting for card...") + try: + sl.wait_for_card(3) + except NoCardError: + print("No card detected!") + return None, None + except: + print("Card not readable!") + return None, None - card = card_detect("auto", scc) - if card is None: - print("Warning: Could not detect card type - assuming a generic card type...") - card = SimCard(scc) + card = card_detect("auto", scc) + if card is None: + print("Warning: Could not detect card type - assuming a generic card type...") + card = SimCard(scc) - profile = CardProfile.pick(scc) - if profile is None: - print("Unsupported card type!") - return None, None + profile = CardProfile.pick(scc) + if profile is None: + print("Unsupported card type!") + return None, None - print("Info: Card is of type: %s" % str(profile)) + print("Info: Card is of type: %s" % str(profile)) - # FIXME: This shouln't be here, the profile should add the applications, - # however, we cannot simply put his into ts_102_221.py since we would - # have to e.g. import CardApplicationUSIM from ts_31_102.py, which already - # imports from ts_102_221.py. This means we will end up with a circular - # import, which needs to be resolved first. - if isinstance(profile, CardProfileUICC): - profile.add_application(CardApplicationUSIM()) - profile.add_application(CardApplicationISIM()) - profile.add_application(CardApplicationARAM()) + # FIXME: This shouln't be here, the profile should add the applications, + # however, we cannot simply put his into ts_102_221.py since we would + # have to e.g. import CardApplicationUSIM from ts_31_102.py, which already + # imports from ts_102_221.py. This means we will end up with a circular + # import, which needs to be resolved first. + if isinstance(profile, CardProfileUICC): + profile.add_application(CardApplicationUSIM()) + profile.add_application(CardApplicationISIM()) + profile.add_application(CardApplicationARAM()) - # Create runtime state with card profile - rs = RuntimeState(card, profile) + # Create runtime state with card profile + rs = RuntimeState(card, profile) - # FIXME: This is an GSM-R related file, it needs to be added throughout, - # the profile. At the moment we add it for all cards, this won't hurt, - # but regular SIM and UICC will not have it and fail to select it. - rs.mf.add_file(DF_EIRENE()) + # FIXME: This is an GSM-R related file, it needs to be added throughout, + # the profile. At the moment we add it for all cards, this won't hurt, + # but regular SIM and UICC will not have it and fail to select it. + rs.mf.add_file(DF_EIRENE()) - CardModel.apply_matching_models(scc, rs) + CardModel.apply_matching_models(scc, rs) - # inform the transport that we can do context-specific SW interpretation - sl.set_sw_interpreter(rs) + # inform the transport that we can do context-specific SW interpretation + sl.set_sw_interpreter(rs) + + return rs, card - return rs, card class PysimApp(cmd2.Cmd): - CUSTOM_CATEGORY = 'pySim Commands' - def __init__(self, card, rs, sl, ch, script = None): - super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False, - use_ipython=True, auto_load_commands=False, startup_script=script) - self.intro = style('Welcome to pySim-shell!', fg=fg.red) - self.default_category = 'pySim-shell built-in commands' - self.card = None - self.rs = None - self.py_locals = { 'card': self.card, 'rs' : self.rs } - self.sl = sl - self.ch = ch + CUSTOM_CATEGORY = 'pySim Commands' - self.numeric_path = False - self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names', - onchange_cb=self._onchange_numeric_path)) - self.conserve_write = True - self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write', - onchange_cb=self._onchange_conserve_write)) - self.json_pretty_print = True - self.add_settable(cmd2.Settable('json_pretty_print', bool, 'Pretty-Print JSON output')) - self.apdu_trace = False - self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card', - onchange_cb=self._onchange_apdu_trace)) + def __init__(self, card, rs, sl, ch, script=None): + super().__init__(persistent_history_file='~/.pysim_shell_history', allow_cli_args=False, + use_ipython=True, auto_load_commands=False, startup_script=script) + self.intro = style('Welcome to pySim-shell!', fg=fg.red) + self.default_category = 'pySim-shell built-in commands' + self.card = None + self.rs = None + self.py_locals = {'card': self.card, 'rs': self.rs} + self.sl = sl + self.ch = ch - self.equip(card, rs) + self.numeric_path = False + self.add_settable(cmd2.Settable('numeric_path', bool, 'Print File IDs instead of names', + onchange_cb=self._onchange_numeric_path)) + self.conserve_write = True + self.add_settable(cmd2.Settable('conserve_write', bool, 'Read and compare before write', + onchange_cb=self._onchange_conserve_write)) + self.json_pretty_print = True + self.add_settable(cmd2.Settable('json_pretty_print', + bool, 'Pretty-Print JSON output')) + self.apdu_trace = False + self.add_settable(cmd2.Settable('apdu_trace', bool, 'Trace and display APDUs exchanged with card', + onchange_cb=self._onchange_apdu_trace)) - def equip(self, card, rs): - """ - Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and - and commands to enable card operations. - """ + self.equip(card, rs) - rc = False + def equip(self, card, rs): + """ + Equip pySim-shell with the supplied card and runtime state, add (or remove) all required settables and + and commands to enable card operations. + """ - # Unequip everything from pySim-shell that would not work in unequipped state - if self.rs: - self.rs.unregister_cmds(self) - for cmds in [Iso7816Commands, PySimCommands]: - cmd_set = self.find_commandsets(cmds) - if cmd_set: - self.unregister_command_set(cmd_set[0]) + rc = False - self.card = card - self.rs = rs + # Unequip everything from pySim-shell that would not work in unequipped state + if self.rs: + self.rs.unregister_cmds(self) + for cmds in [Iso7816Commands, PySimCommands]: + cmd_set = self.find_commandsets(cmds) + if cmd_set: + self.unregister_command_set(cmd_set[0]) - # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is - # needed to operate on cards. - if self.card and self.rs: - self._onchange_conserve_write('conserve_write', False, self.conserve_write) - self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace) - self.register_command_set(Iso7816Commands()) - self.register_command_set(PySimCommands()) - self.iccid, sw = self.card.read_iccid() - rs.select('MF', self) - rc = True - else: - self.poutput("pySim-shell not equipped!") + self.card = card + self.rs = rs - self.update_prompt() - return rc + # When a card object and a runtime state is present, (re)equip pySim-shell with everything that is + # needed to operate on cards. + if self.card and self.rs: + self._onchange_conserve_write( + 'conserve_write', False, self.conserve_write) + self._onchange_apdu_trace('apdu_trace', False, self.apdu_trace) + self.register_command_set(Iso7816Commands()) + self.register_command_set(PySimCommands()) + self.iccid, sw = self.card.read_iccid() + rs.select('MF', self) + rc = True + else: + self.poutput("pySim-shell not equipped!") - def poutput_json(self, data, force_no_pretty = False): - """like cmd2.poutput() but for a JSON serializable dict.""" - if force_no_pretty or self.json_pretty_print == False: - output = json.dumps(data, cls=JsonEncoder) - else: - output = json.dumps(data, cls=JsonEncoder, indent=4) - self.poutput(output) + self.update_prompt() + return rc - def _onchange_numeric_path(self, param_name, old, new): - self.update_prompt() + def poutput_json(self, data, force_no_pretty=False): + """like cmd2.poutput() but for a JSON serializable dict.""" + if force_no_pretty or self.json_pretty_print == False: + output = json.dumps(data, cls=JsonEncoder) + else: + output = json.dumps(data, cls=JsonEncoder, indent=4) + self.poutput(output) - def _onchange_conserve_write(self, param_name, old, new): - if self.rs: - self.rs.conserve_write = new + def _onchange_numeric_path(self, param_name, old, new): + self.update_prompt() - def _onchange_apdu_trace(self, param_name, old, new): - if self.card: - if new == True: - self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self) - else: - self.card._scc._tp.apdu_tracer = None + def _onchange_conserve_write(self, param_name, old, new): + if self.rs: + self.rs.conserve_write = new - class Cmd2ApduTracer(ApduTracer): - def __init__(self, cmd2_app): - self.cmd2 = app + def _onchange_apdu_trace(self, param_name, old, new): + if self.card: + if new == True: + self.card._scc._tp.apdu_tracer = self.Cmd2ApduTracer(self) + else: + self.card._scc._tp.apdu_tracer = None - def trace_response(self, cmd, sw, resp): - self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:])) - self.cmd2.poutput("<- %s: %s" % (sw, resp)) + class Cmd2ApduTracer(ApduTracer): + def __init__(self, cmd2_app): + self.cmd2 = app - def update_prompt(self): - if self.rs: - path_list = self.rs.selected_file.fully_qualified_path(not self.numeric_path) - self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list)) - else: - self.prompt = 'pySIM-shell (no card)> ' + def trace_response(self, cmd, sw, resp): + self.cmd2.poutput("-> %s %s" % (cmd[:10], cmd[10:])) + self.cmd2.poutput("<- %s: %s" % (sw, resp)) - @cmd2.with_category(CUSTOM_CATEGORY) - def do_intro(self, _): - """Display the intro banner""" - self.poutput(self.intro) + def update_prompt(self): + if self.rs: + path_list = self.rs.selected_file.fully_qualified_path( + not self.numeric_path) + self.prompt = 'pySIM-shell (%s)> ' % ('/'.join(path_list)) + else: + self.prompt = 'pySIM-shell (no card)> ' - def do_eof(self, _: argparse.Namespace) -> bool: - self.poutput("") - return self.do_quit('') + @cmd2.with_category(CUSTOM_CATEGORY) + def do_intro(self, _): + """Display the intro banner""" + self.poutput(self.intro) - @cmd2.with_category(CUSTOM_CATEGORY) - def do_equip(self, opts): - """Equip pySim-shell with card""" - rs, card = init_card(sl) - self.equip(card, rs) + def do_eof(self, _: argparse.Namespace) -> bool: + self.poutput("") + return self.do_quit('') - class InterceptStderr(list): - def __init__(self): - self._stderr_backup = sys.stderr - def __enter__(self): - self._stringio_stderr = StringIO() - sys.stderr = self._stringio_stderr - return self - def __exit__(self, *args): - self.stderr = self._stringio_stderr.getvalue().strip() - del self._stringio_stderr - sys.stderr = self._stderr_backup + @cmd2.with_category(CUSTOM_CATEGORY) + def do_equip(self, opts): + """Equip pySim-shell with card""" + rs, card = init_card(sl) + self.equip(card, rs) - def _show_failure_sign(self): - self.poutput(style(" +-------------+", fg=fg.bright_red)) - self.poutput(style(" + ## ## +", fg=fg.bright_red)) - self.poutput(style(" + ## ## +", fg=fg.bright_red)) - self.poutput(style(" + ### +", fg=fg.bright_red)) - self.poutput(style(" + ## ## +", fg=fg.bright_red)) - self.poutput(style(" + ## ## +", fg=fg.bright_red)) - self.poutput(style(" +-------------+", fg=fg.bright_red)) - self.poutput("") + class InterceptStderr(list): + def __init__(self): + self._stderr_backup = sys.stderr - def _show_success_sign(self): - self.poutput(style(" +-------------+", fg=fg.bright_green)) - self.poutput(style(" + ## +", fg=fg.bright_green)) - self.poutput(style(" + ## +", fg=fg.bright_green)) - self.poutput(style(" + # ## +", fg=fg.bright_green)) - self.poutput(style(" + ## # +", fg=fg.bright_green)) - self.poutput(style(" + ## +", fg=fg.bright_green)) - self.poutput(style(" +-------------+", fg=fg.bright_green)) - self.poutput("") + def __enter__(self): + self._stringio_stderr = StringIO() + sys.stderr = self._stringio_stderr + return self - def _process_card(self, first, script_path): + def __exit__(self, *args): + self.stderr = self._stringio_stderr.getvalue().strip() + del self._stringio_stderr + sys.stderr = self._stderr_backup - # Early phase of card initialzation (this part may fail with an exception) - try: - rs, card = init_card(self.sl) - rc = self.equip(card, rs) - except: - self.poutput("") - self.poutput("Card initialization failed with an exception:") - self.poutput("---------------------8<---------------------") - traceback.print_exc() - self.poutput("---------------------8<---------------------") - self.poutput("") - return -1 + def _show_failure_sign(self): + self.poutput(style(" +-------------+", fg=fg.bright_red)) + self.poutput(style(" + ## ## +", fg=fg.bright_red)) + self.poutput(style(" + ## ## +", fg=fg.bright_red)) + self.poutput(style(" + ### +", fg=fg.bright_red)) + self.poutput(style(" + ## ## +", fg=fg.bright_red)) + self.poutput(style(" + ## ## +", fg=fg.bright_red)) + self.poutput(style(" +-------------+", fg=fg.bright_red)) + self.poutput("") - # Actual card processing step. This part should never fail with an exception since the cmd2 - # do_run_script method will catch any exception that might occur during script execution. - if rc: - self.poutput("") - self.poutput("Transcript stdout:") - self.poutput("---------------------8<---------------------") - with self.InterceptStderr() as logged: - self.do_run_script(script_path) - self.poutput("---------------------8<---------------------") + def _show_success_sign(self): + self.poutput(style(" +-------------+", fg=fg.bright_green)) + self.poutput(style(" + ## +", fg=fg.bright_green)) + self.poutput(style(" + ## +", fg=fg.bright_green)) + self.poutput(style(" + # ## +", fg=fg.bright_green)) + self.poutput(style(" + ## # +", fg=fg.bright_green)) + self.poutput(style(" + ## +", fg=fg.bright_green)) + self.poutput(style(" +-------------+", fg=fg.bright_green)) + self.poutput("") - self.poutput("") - self.poutput("Transcript stderr:") - if logged.stderr: - self.poutput("---------------------8<---------------------") - self.poutput(logged.stderr) - self.poutput("---------------------8<---------------------") - else: - self.poutput("(none)") + def _process_card(self, first, script_path): - # Check for exceptions - self.poutput("") - if "EXCEPTION of type" not in logged.stderr: - return 0 + # Early phase of card initialzation (this part may fail with an exception) + try: + rs, card = init_card(self.sl) + rc = self.equip(card, rs) + except: + self.poutput("") + self.poutput("Card initialization failed with an exception:") + self.poutput("---------------------8<---------------------") + traceback.print_exc() + self.poutput("---------------------8<---------------------") + self.poutput("") + return -1 - return -1 + # Actual card processing step. This part should never fail with an exception since the cmd2 + # do_run_script method will catch any exception that might occur during script execution. + if rc: + self.poutput("") + self.poutput("Transcript stdout:") + self.poutput("---------------------8<---------------------") + with self.InterceptStderr() as logged: + self.do_run_script(script_path) + self.poutput("---------------------8<---------------------") - bulk_script_parser = argparse.ArgumentParser() - bulk_script_parser.add_argument('script_path', help="path to the script file") - bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs', - action='store_true') - bulk_script_parser.add_argument('--tries', type=int, default=2, - help='how many tries before trying the next card') - bulk_script_parser.add_argument('--on_stop_action', type=str, default=None, - help='commandline to execute when card handling has stopped') - bulk_script_parser.add_argument('--pre_card_action', type=str, default=None, - help='commandline to execute before actually talking to the card') + self.poutput("") + self.poutput("Transcript stderr:") + if logged.stderr: + self.poutput("---------------------8<---------------------") + self.poutput(logged.stderr) + self.poutput("---------------------8<---------------------") + else: + self.poutput("(none)") - @cmd2.with_argparser(bulk_script_parser) - @cmd2.with_category(CUSTOM_CATEGORY) - def do_bulk_script(self, opts): - """Run script on multiple cards (bulk provisioning)""" + # Check for exceptions + self.poutput("") + if "EXCEPTION of type" not in logged.stderr: + return 0 - # Make sure that the script file exists and that it is readable. - if not os.access(opts.script_path, os.R_OK): - self.poutput("Invalid script file!") - return + return -1 - success_count = 0 - fail_count = 0 + bulk_script_parser = argparse.ArgumentParser() + bulk_script_parser.add_argument( + 'script_path', help="path to the script file") + bulk_script_parser.add_argument('--halt_on_error', help='stop card handling if an exeption occurs', + action='store_true') + bulk_script_parser.add_argument('--tries', type=int, default=2, + help='how many tries before trying the next card') + bulk_script_parser.add_argument('--on_stop_action', type=str, default=None, + help='commandline to execute when card handling has stopped') + bulk_script_parser.add_argument('--pre_card_action', type=str, default=None, + help='commandline to execute before actually talking to the card') - first = True - while 1: - # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop. - # The ratinale is: There may be a problem with the device, we do want to prevent that - # all remaining cards are fired to the error bin. This is only relevant for situations - # with large stacks, probably we do not need this feature right now. + @cmd2.with_argparser(bulk_script_parser) + @cmd2.with_category(CUSTOM_CATEGORY) + def do_bulk_script(self, opts): + """Run script on multiple cards (bulk provisioning)""" - try: - # In case of failure, try multiple times. - for i in range(opts.tries): - # fetch card into reader bay - ch.get(first) + # Make sure that the script file exists and that it is readable. + if not os.access(opts.script_path, os.R_OK): + self.poutput("Invalid script file!") + return - # if necessary execute an action before we start processing the card - if(opts.pre_card_action): - os.system(opts.pre_card_action) + success_count = 0 + fail_count = 0 - # process the card - rc = self._process_card(first, opts.script_path) - if rc == 0: - success_count = success_count + 1 - self._show_success_sign() - self.poutput("Statistics: success :%i, failure: %i" % (success_count, fail_count)) - break - else: - fail_count = fail_count + 1 - self._show_failure_sign() - self.poutput("Statistics: success :%i, failure: %i" % (success_count, fail_count)) + first = True + while 1: + # TODO: Count consecutive failures, if more than N consecutive failures occur, then stop. + # The ratinale is: There may be a problem with the device, we do want to prevent that + # all remaining cards are fired to the error bin. This is only relevant for situations + # with large stacks, probably we do not need this feature right now. + try: + # In case of failure, try multiple times. + for i in range(opts.tries): + # fetch card into reader bay + ch.get(first) - # Depending on success or failure, the card goes either in the "error" bin or in the - # "done" bin. - if rc < 0: - ch.error() - else: - ch.done() + # if necessary execute an action before we start processing the card + if(opts.pre_card_action): + os.system(opts.pre_card_action) - # In most cases it is possible to proceed with the next card, but the - # user may decide to halt immediately when an error occurs - if opts.halt_on_error and rc < 0: - return + # process the card + rc = self._process_card(first, opts.script_path) + if rc == 0: + success_count = success_count + 1 + self._show_success_sign() + self.poutput("Statistics: success :%i, failure: %i" % ( + success_count, fail_count)) + break + else: + fail_count = fail_count + 1 + self._show_failure_sign() + self.poutput("Statistics: success :%i, failure: %i" % ( + success_count, fail_count)) - except (KeyboardInterrupt): - self.poutput("") - self.poutput("Terminated by user!") - return - except (SystemExit): - # When all cards are processed the card handler device will throw a SystemExit - # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here. - # The user has the option to execute some action to make aware that the card handler - # needs service. - if(opts.on_stop_action): - os.system(opts.on_stop_action) - return - except: - self.poutput("") - self.poutput("Card handling failed with an exception:") - self.poutput("---------------------8<---------------------") - traceback.print_exc() - self.poutput("---------------------8<---------------------") - self.poutput("") - fail_count = fail_count + 1 - self._show_failure_sign() - self.poutput("Statistics: success :%i, failure: %i" % (success_count, fail_count)) + # Depending on success or failure, the card goes either in the "error" bin or in the + # "done" bin. + if rc < 0: + ch.error() + else: + ch.done() - first = False + # In most cases it is possible to proceed with the next card, but the + # user may decide to halt immediately when an error occurs + if opts.halt_on_error and rc < 0: + return - echo_parser = argparse.ArgumentParser() - echo_parser.add_argument('string', help="string to echo on the shell") + except (KeyboardInterrupt): + self.poutput("") + self.poutput("Terminated by user!") + return + except (SystemExit): + # When all cards are processed the card handler device will throw a SystemExit + # exception. Also Errors that are not recoverable (cards stuck etc.) will end up here. + # The user has the option to execute some action to make aware that the card handler + # needs service. + if(opts.on_stop_action): + os.system(opts.on_stop_action) + return + except: + self.poutput("") + self.poutput("Card handling failed with an exception:") + self.poutput("---------------------8<---------------------") + traceback.print_exc() + self.poutput("---------------------8<---------------------") + self.poutput("") + fail_count = fail_count + 1 + self._show_failure_sign() + self.poutput("Statistics: success :%i, failure: %i" % + (success_count, fail_count)) + + first = False + + echo_parser = argparse.ArgumentParser() + echo_parser.add_argument('string', help="string to echo on the shell") + + @cmd2.with_argparser(echo_parser) + @cmd2.with_category(CUSTOM_CATEGORY) + def do_echo(self, opts): + """Echo (print) a string on the console""" + self.poutput(opts.string) - @cmd2.with_argparser(echo_parser) - @cmd2.with_category(CUSTOM_CATEGORY) - def do_echo(self, opts): - """Echo (print) a string on the console""" - self.poutput(opts.string) @with_default_category('pySim Commands') class PySimCommands(CommandSet): - def __init__(self): - super().__init__() + def __init__(self): + super().__init__() - dir_parser = argparse.ArgumentParser() - dir_parser.add_argument('--fids', help='Show file identifiers', action='store_true') - dir_parser.add_argument('--names', help='Show file names', action='store_true') - dir_parser.add_argument('--apps', help='Show applications', action='store_true') - dir_parser.add_argument('--all', help='Show all selectable identifiers and names', action='store_true') + dir_parser = argparse.ArgumentParser() + dir_parser.add_argument( + '--fids', help='Show file identifiers', action='store_true') + dir_parser.add_argument( + '--names', help='Show file names', action='store_true') + dir_parser.add_argument( + '--apps', help='Show applications', action='store_true') + dir_parser.add_argument( + '--all', help='Show all selectable identifiers and names', action='store_true') - @cmd2.with_argparser(dir_parser) - def do_dir(self, opts): - """Show a listing of files available in currently selected DF or MF""" - if opts.all: - flags = [] - elif opts.fids or opts.names or opts.apps: - flags = ['PARENT', 'SELF'] - if opts.fids: - flags += ['FIDS', 'AIDS'] - if opts.names: - flags += ['FNAMES', 'ANAMES'] - if opts.apps: - flags += ['ANAMES', 'AIDS'] - else: - flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES'] - selectables = list(self._cmd.rs.selected_file.get_selectable_names(flags = flags)) - directory_str = tabulate_str_list(selectables, width = 79, hspace = 2, lspace = 1, align_left = True) - path_list = self._cmd.rs.selected_file.fully_qualified_path(True) - self._cmd.poutput('/'.join(path_list)) - path_list = self._cmd.rs.selected_file.fully_qualified_path(False) - self._cmd.poutput('/'.join(path_list)) - self._cmd.poutput(directory_str) - self._cmd.poutput("%d files" % len(selectables)) + @cmd2.with_argparser(dir_parser) + def do_dir(self, opts): + """Show a listing of files available in currently selected DF or MF""" + if opts.all: + flags = [] + elif opts.fids or opts.names or opts.apps: + flags = ['PARENT', 'SELF'] + if opts.fids: + flags += ['FIDS', 'AIDS'] + if opts.names: + flags += ['FNAMES', 'ANAMES'] + if opts.apps: + flags += ['ANAMES', 'AIDS'] + else: + flags = ['PARENT', 'SELF', 'FNAMES', 'ANAMES'] + selectables = list( + self._cmd.rs.selected_file.get_selectable_names(flags=flags)) + directory_str = tabulate_str_list( + selectables, width=79, hspace=2, lspace=1, align_left=True) + path_list = self._cmd.rs.selected_file.fully_qualified_path(True) + self._cmd.poutput('/'.join(path_list)) + path_list = self._cmd.rs.selected_file.fully_qualified_path(False) + self._cmd.poutput('/'.join(path_list)) + self._cmd.poutput(directory_str) + self._cmd.poutput("%d files" % len(selectables)) - def walk(self, indent = 0, action = None, context = None): - """Recursively walk through the file system, starting at the currently selected DF""" - files = self._cmd.rs.selected_file.get_selectables(flags = ['FNAMES', 'ANAMES']) - for f in files: - if not action: - output_str = " " * indent + str(f) + (" " * 250) - output_str = output_str[0:25] - if isinstance(files[f], CardADF): - output_str += " " + str(files[f].aid) - else: - output_str += " " + str(files[f].fid) - output_str += " " + str(files[f].desc) - self._cmd.poutput(output_str) + def walk(self, indent=0, action=None, context=None): + """Recursively walk through the file system, starting at the currently selected DF""" + files = self._cmd.rs.selected_file.get_selectables( + flags=['FNAMES', 'ANAMES']) + for f in files: + if not action: + output_str = " " * indent + str(f) + (" " * 250) + output_str = output_str[0:25] + if isinstance(files[f], CardADF): + output_str += " " + str(files[f].aid) + else: + output_str += " " + str(files[f].fid) + output_str += " " + str(files[f].desc) + self._cmd.poutput(output_str) - if isinstance(files[f], CardDF): - skip_df=False - try: - fcp_dec = self._cmd.rs.select(f, self._cmd) - except Exception as e: - skip_df=True - df = self._cmd.rs.selected_file - df_path_list = df.fully_qualified_path(True) - df_skip_reason_str = '/'.join(df_path_list) + "/" + str(f) + ", " + str(e) - if context: - context['DF_SKIP'] += 1 - context['DF_SKIP_REASON'].append(df_skip_reason_str) + if isinstance(files[f], CardDF): + skip_df = False + try: + fcp_dec = self._cmd.rs.select(f, self._cmd) + except Exception as e: + skip_df = True + df = self._cmd.rs.selected_file + df_path_list = df.fully_qualified_path(True) + df_skip_reason_str = '/'.join(df_path_list) + \ + "/" + str(f) + ", " + str(e) + if context: + context['DF_SKIP'] += 1 + context['DF_SKIP_REASON'].append(df_skip_reason_str) - # If the DF was skipped, we never have entered the directory - # below, so we must not move up. - if skip_df == False: - self.walk(indent + 1, action, context) - fcp_dec = self._cmd.rs.select("..", self._cmd) + # If the DF was skipped, we never have entered the directory + # below, so we must not move up. + if skip_df == False: + self.walk(indent + 1, action, context) + fcp_dec = self._cmd.rs.select("..", self._cmd) - elif action: - df_before_action = self._cmd.rs.selected_file - action(f, context) - # When walking through the file system tree the action must not - # always restore the currently selected file to the file that - # was selected before executing the action() callback. - if df_before_action != self._cmd.rs.selected_file: - raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected" - % (str(self._cmd.rs.selected_file), str(df_before_action))) + elif action: + df_before_action = self._cmd.rs.selected_file + action(f, context) + # When walking through the file system tree the action must not + # always restore the currently selected file to the file that + # was selected before executing the action() callback. + if df_before_action != self._cmd.rs.selected_file: + raise RuntimeError("inconsistent walk, %s is currently selected but expecting %s to be selected" + % (str(self._cmd.rs.selected_file), str(df_before_action))) - def do_tree(self, opts): - """Display a filesystem-tree with all selectable files""" - self.walk() + def do_tree(self, opts): + """Display a filesystem-tree with all selectable files""" + self.walk() - def export(self, filename, context): - """ Select and export a single file """ - context['COUNT'] += 1 - df = self._cmd.rs.selected_file + def export(self, filename, context): + """ Select and export a single file """ + context['COUNT'] += 1 + df = self._cmd.rs.selected_file - if not isinstance(df, CardDF): - raise RuntimeError("currently selected file %s is not a DF or ADF" % str(df)) + if not isinstance(df, CardDF): + raise RuntimeError( + "currently selected file %s is not a DF or ADF" % str(df)) - df_path_list = df.fully_qualified_path(True) - df_path_list_fid = df.fully_qualified_path(False) + df_path_list = df.fully_qualified_path(True) + df_path_list_fid = df.fully_qualified_path(False) - file_str = '/'.join(df_path_list) + "/" + str(filename) - self._cmd.poutput(boxed_heading_str(file_str)) + file_str = '/'.join(df_path_list) + "/" + str(filename) + self._cmd.poutput(boxed_heading_str(file_str)) - self._cmd.poutput("# directory: %s (%s)" % ('/'.join(df_path_list), '/'.join(df_path_list_fid))) - try: - fcp_dec = self._cmd.rs.select(filename, self._cmd) - self._cmd.poutput("# file: %s (%s)" % (self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid)) + self._cmd.poutput("# directory: %s (%s)" % + ('/'.join(df_path_list), '/'.join(df_path_list_fid))) + try: + fcp_dec = self._cmd.rs.select(filename, self._cmd) + self._cmd.poutput("# file: %s (%s)" % ( + self._cmd.rs.selected_file.name, self._cmd.rs.selected_file.fid)) - fd = fcp_dec['file_descriptor'] - structure = fd['structure'] - self._cmd.poutput("# structure: %s" % str(structure)) + fd = fcp_dec['file_descriptor'] + structure = fd['structure'] + self._cmd.poutput("# structure: %s" % str(structure)) - for f in df_path_list: - self._cmd.poutput("select " + str(f)) - self._cmd.poutput("select " + self._cmd.rs.selected_file.name) + for f in df_path_list: + self._cmd.poutput("select " + str(f)) + self._cmd.poutput("select " + self._cmd.rs.selected_file.name) - if structure == 'transparent': - result = self._cmd.rs.read_binary() - self._cmd.poutput("update_binary " + str(result[0])) - elif structure == 'cyclic' or structure == 'linear_fixed': - # Use number of records specified in select response - if 'num_of_rec' in fd: - num_of_rec = fd['num_of_rec'] - for r in range(1, num_of_rec + 1): - result = self._cmd.rs.read_record(r) - self._cmd.poutput("update_record %d %s" % (r, str(result[0]))) - # When the select response does not return the number of records, read until we hit the - # first record that cannot be read. - else: - r = 1 - while True: - try: - result = self._cmd.rs.read_record(r) - except SwMatchError as e: - # We are past the last valid record - stop - if e.sw_actual == "9402": - break - # Some other problem occurred - else: - raise e - self._cmd.poutput("update_record %d %s" % (r, str(result[0]))) - r = r + 1 - elif structure == 'ber_tlv': - tags = self._cmd.rs.retrieve_tags() - for t in tags: - result = self._cmd.rs.retrieve_data(t) - (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0])) - self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val))) - else: - raise RuntimeError('Unsupported structure "%s" of file "%s"' % (structure, filename)) - except Exception as e: - bad_file_str = '/'.join(df_path_list) + "/" + str(filename) + ", " + str(e) - self._cmd.poutput("# bad file: %s" % bad_file_str) - context['ERR'] += 1 - context['BAD'].append(bad_file_str) + if structure == 'transparent': + result = self._cmd.rs.read_binary() + self._cmd.poutput("update_binary " + str(result[0])) + elif structure == 'cyclic' or structure == 'linear_fixed': + # Use number of records specified in select response + if 'num_of_rec' in fd: + num_of_rec = fd['num_of_rec'] + for r in range(1, num_of_rec + 1): + result = self._cmd.rs.read_record(r) + self._cmd.poutput("update_record %d %s" % + (r, str(result[0]))) + # When the select response does not return the number of records, read until we hit the + # first record that cannot be read. + else: + r = 1 + while True: + try: + result = self._cmd.rs.read_record(r) + except SwMatchError as e: + # We are past the last valid record - stop + if e.sw_actual == "9402": + break + # Some other problem occurred + else: + raise e + self._cmd.poutput("update_record %d %s" % + (r, str(result[0]))) + r = r + 1 + elif structure == 'ber_tlv': + tags = self._cmd.rs.retrieve_tags() + for t in tags: + result = self._cmd.rs.retrieve_data(t) + (tag, l, val, remainer) = bertlv_parse_one(h2b(result[0])) + self._cmd.poutput("set_data 0x%02x %s" % (t, b2h(val))) + else: + raise RuntimeError( + 'Unsupported structure "%s" of file "%s"' % (structure, filename)) + except Exception as e: + bad_file_str = '/'.join(df_path_list) + \ + "/" + str(filename) + ", " + str(e) + self._cmd.poutput("# bad file: %s" % bad_file_str) + context['ERR'] += 1 + context['BAD'].append(bad_file_str) - # When reading the file is done, make sure the parent file is - # selected again. This will be the usual case, however we need - # to check before since we must not select the same DF twice - if df != self._cmd.rs.selected_file: - self._cmd.rs.select(df.fid or df.aid, self._cmd) + # When reading the file is done, make sure the parent file is + # selected again. This will be the usual case, however we need + # to check before since we must not select the same DF twice + if df != self._cmd.rs.selected_file: + self._cmd.rs.select(df.fid or df.aid, self._cmd) - self._cmd.poutput("#") + self._cmd.poutput("#") - export_parser = argparse.ArgumentParser() - export_parser.add_argument('--filename', type=str, default=None, help='only export specific file') + export_parser = argparse.ArgumentParser() + export_parser.add_argument( + '--filename', type=str, default=None, help='only export specific file') - @cmd2.with_argparser(export_parser) - def do_export(self, opts): - """Export files to script that can be imported back later""" - context = {'ERR':0, 'COUNT':0, 'BAD':[], 'DF_SKIP':0, 'DF_SKIP_REASON':[]} - if opts.filename: - self.export(opts.filename, context) - else: - self.walk(0, self.export, context) + @cmd2.with_argparser(export_parser) + def do_export(self, opts): + """Export files to script that can be imported back later""" + context = {'ERR': 0, 'COUNT': 0, 'BAD': [], + 'DF_SKIP': 0, 'DF_SKIP_REASON': []} + if opts.filename: + self.export(opts.filename, context) + else: + self.walk(0, self.export, context) - self._cmd.poutput(boxed_heading_str("Export summary")) + self._cmd.poutput(boxed_heading_str("Export summary")) - self._cmd.poutput("# total files visited: %u" % context['COUNT']) - self._cmd.poutput("# bad files: %u" % context['ERR']) - for b in context['BAD']: - self._cmd.poutput("# " + b) + self._cmd.poutput("# total files visited: %u" % context['COUNT']) + self._cmd.poutput("# bad files: %u" % context['ERR']) + for b in context['BAD']: + self._cmd.poutput("# " + b) - self._cmd.poutput("# skipped dedicated files(s): %u" % context['DF_SKIP']) - for b in context['DF_SKIP_REASON']: - self._cmd.poutput("# " + b) + self._cmd.poutput("# skipped dedicated files(s): %u" % + context['DF_SKIP']) + for b in context['DF_SKIP_REASON']: + self._cmd.poutput("# " + b) - if context['ERR'] and context['DF_SKIP']: - raise RuntimeError("unable to export %i elementary file(s) and %i dedicated file(s)" % (context['ERR'], context['DF_SKIP'])) - elif context['ERR']: - raise RuntimeError("unable to export %i elementary file(s)" % context['ERR']) - elif context['DF_SKIP']: - raise RuntimeError("unable to export %i dedicated files(s)" % context['ERR']) + 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'])) + elif context['ERR']: + raise RuntimeError( + "unable to export %i elementary file(s)" % context['ERR']) + elif context['DF_SKIP']: + raise RuntimeError( + "unable to export %i dedicated files(s)" % context['ERR']) - def do_reset(self, opts): - """Reset the Card.""" - atr = self._cmd.rs.reset(self._cmd) - self._cmd.poutput('Card ATR: %s' % atr) - self._cmd.update_prompt() + def do_reset(self, opts): + """Reset the Card.""" + atr = self._cmd.rs.reset(self._cmd) + self._cmd.poutput('Card ATR: %s' % atr) + self._cmd.update_prompt() - def do_desc(self, opts): - """Display human readable file description for the currently selected file""" - desc = self._cmd.rs.selected_file.desc - if desc: - self._cmd.poutput(desc) - else: - self._cmd.poutput("no description available") + def do_desc(self, opts): + """Display human readable file description for the currently selected file""" + desc = self._cmd.rs.selected_file.desc + if desc: + self._cmd.poutput(desc) + else: + self._cmd.poutput("no description available") - def do_verify_adm(self, arg): - """VERIFY the ADM1 PIN""" - if arg: - # use specified ADM-PIN - pin_adm = sanitize_pin_adm(arg) - else: - # try to find an ADM-PIN if none is specified - result = card_key_provider_get_field('ADM1', key='ICCID', value=self._cmd.iccid) - pin_adm = sanitize_pin_adm(result) - if pin_adm: - self._cmd.poutput("found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid)) - else: - raise ValueError("cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid)) + def do_verify_adm(self, arg): + """VERIFY the ADM1 PIN""" + if arg: + # use specified ADM-PIN + pin_adm = sanitize_pin_adm(arg) + else: + # try to find an ADM-PIN if none is specified + result = card_key_provider_get_field( + 'ADM1', key='ICCID', value=self._cmd.iccid) + pin_adm = sanitize_pin_adm(result) + if pin_adm: + self._cmd.poutput( + "found ADM-PIN '%s' for ICCID '%s'" % (result, self._cmd.iccid)) + else: + raise ValueError( + "cannot find ADM-PIN for ICCID '%s'" % (self._cmd.iccid)) + + if pin_adm: + self._cmd.card.verify_adm(h2b(pin_adm)) + else: + raise ValueError("error: cannot authenticate, no adm-pin!") - if pin_adm: - self._cmd.card.verify_adm(h2b(pin_adm)) - else: - raise ValueError("error: cannot authenticate, no adm-pin!") @with_default_category('ISO7816 Commands') class Iso7816Commands(CommandSet): - def __init__(self): - super().__init__() + def __init__(self): + super().__init__() - def do_select(self, opts): - """SELECT a File (ADF/DF/EF)""" - if len(opts.arg_list) == 0: - path_list = self._cmd.rs.selected_file.fully_qualified_path(True) - path_list_fid = self._cmd.rs.selected_file.fully_qualified_path(False) - self._cmd.poutput("currently selected file: " + '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")") - return + def do_select(self, opts): + """SELECT a File (ADF/DF/EF)""" + if len(opts.arg_list) == 0: + path_list = self._cmd.rs.selected_file.fully_qualified_path(True) + path_list_fid = self._cmd.rs.selected_file.fully_qualified_path( + False) + self._cmd.poutput("currently selected file: " + + '/'.join(path_list) + " (" + '/'.join(path_list_fid) + ")") + return - path = opts.arg_list[0] - fcp_dec = self._cmd.rs.select(path, self._cmd) - self._cmd.update_prompt() - self._cmd.poutput_json(fcp_dec) + path = opts.arg_list[0] + fcp_dec = self._cmd.rs.select(path, self._cmd) + self._cmd.update_prompt() + self._cmd.poutput_json(fcp_dec) - def complete_select(self, text, line, begidx, endidx) -> List[str]: - """Command Line tab completion for SELECT""" - 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) + def complete_select(self, text, line, begidx, endidx) -> List[str]: + """Command Line tab completion for SELECT""" + 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) - def get_code(self, code): - """Use code either directly or try to get it from external data source""" - auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2') + def get_code(self, code): + """Use code either directly or try to get it from external data source""" + auto = ('PIN1', 'PIN2', 'PUK1', 'PUK2') - if str(code).upper() not in auto: - return sanitize_pin_adm(code) + if str(code).upper() not in auto: + return sanitize_pin_adm(code) - result = card_key_provider_get_field(str(code), key='ICCID', value=self._cmd.iccid) - result = sanitize_pin_adm(result) - if result: - self._cmd.poutput("found %s '%s' for ICCID '%s'" % (code.upper(), result, self._cmd.iccid)) - else: - self._cmd.poutput("cannot find %s for ICCID '%s'" % (code.upper(), self._cmd.iccid)) - return result + result = card_key_provider_get_field( + str(code), key='ICCID', value=self._cmd.iccid) + result = sanitize_pin_adm(result) + if result: + self._cmd.poutput("found %s '%s' for ICCID '%s'" % + (code.upper(), result, self._cmd.iccid)) + else: + self._cmd.poutput("cannot find %s for ICCID '%s'" % + (code.upper(), self._cmd.iccid)) + return result - verify_chv_parser = argparse.ArgumentParser() - verify_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)') - verify_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source') + verify_chv_parser = argparse.ArgumentParser() + verify_chv_parser.add_argument( + '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)') + verify_chv_parser.add_argument( + 'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source') - @cmd2.with_argparser(verify_chv_parser) - def do_verify_chv(self, opts): - """Verify (authenticate) using specified PIN code""" - pin = self.get_code(opts.pin_code) - (data, sw) = self._cmd.card._scc.verify_chv(opts.pin_nr, h2b(pin)) - self._cmd.poutput("CHV verification successful") + @cmd2.with_argparser(verify_chv_parser) + def do_verify_chv(self, opts): + """Verify (authenticate) using specified PIN code""" + pin = self.get_code(opts.pin_code) + (data, sw) = self._cmd.card._scc.verify_chv(opts.pin_nr, h2b(pin)) + self._cmd.poutput("CHV verification successful") - unblock_chv_parser = argparse.ArgumentParser() - unblock_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)') - unblock_chv_parser.add_argument('puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source') - unblock_chv_parser.add_argument('new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source') + unblock_chv_parser = argparse.ArgumentParser() + unblock_chv_parser.add_argument( + '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)') + unblock_chv_parser.add_argument( + 'puk_code', type=str, help='PUK code digits \"PUK1\" or \"PUK2\" to get PUK code from external data source') + unblock_chv_parser.add_argument( + 'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source') - @cmd2.with_argparser(unblock_chv_parser) - def do_unblock_chv(self, opts): - """Unblock PIN code using specified PUK code""" - new_pin = self.get_code(opts.new_pin_code) - puk = self.get_code(opts.puk_code) - (data, sw) = self._cmd.card._scc.unblock_chv(opts.pin_nr, h2b(puk), h2b(new_pin)) - self._cmd.poutput("CHV unblock successful") + @cmd2.with_argparser(unblock_chv_parser) + def do_unblock_chv(self, opts): + """Unblock PIN code using specified PUK code""" + new_pin = self.get_code(opts.new_pin_code) + puk = self.get_code(opts.puk_code) + (data, sw) = self._cmd.card._scc.unblock_chv( + opts.pin_nr, h2b(puk), h2b(new_pin)) + self._cmd.poutput("CHV unblock successful") - change_chv_parser = argparse.ArgumentParser() - change_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)') - change_chv_parser.add_argument('pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source') - change_chv_parser.add_argument('new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source') + change_chv_parser = argparse.ArgumentParser() + change_chv_parser.add_argument( + '--pin-nr', type=int, default=1, help='PUK Number, 1=PIN1, 2=PIN2 or custom value (decimal)') + change_chv_parser.add_argument( + 'pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source') + change_chv_parser.add_argument( + 'new_pin_code', type=str, help='PIN code digits \"PIN1\" or \"PIN2\" to get PIN code from external data source') - @cmd2.with_argparser(change_chv_parser) - def do_change_chv(self, opts): - """Change PIN code to a new PIN code""" - new_pin = self.get_code(opts.new_pin_code) - pin = self.get_code(opts.pin_code) - (data, sw) = self._cmd.card._scc.change_chv(opts.pin_nr, h2b(pin), h2b(new_pin)) - self._cmd.poutput("CHV change successful") + @cmd2.with_argparser(change_chv_parser) + def do_change_chv(self, opts): + """Change PIN code to a new PIN code""" + new_pin = self.get_code(opts.new_pin_code) + pin = self.get_code(opts.pin_code) + (data, sw) = self._cmd.card._scc.change_chv( + opts.pin_nr, h2b(pin), h2b(new_pin)) + self._cmd.poutput("CHV change successful") - disable_chv_parser = argparse.ArgumentParser() - disable_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)') - disable_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source') + disable_chv_parser = argparse.ArgumentParser() + disable_chv_parser.add_argument( + '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)') + disable_chv_parser.add_argument( + 'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source') - @cmd2.with_argparser(disable_chv_parser) - def do_disable_chv(self, opts): - """Disable PIN code using specified PIN code""" - pin = self.get_code(opts.pin_code) - (data, sw) = self._cmd.card._scc.disable_chv(opts.pin_nr, h2b(pin)) - self._cmd.poutput("CHV disable successful") + @cmd2.with_argparser(disable_chv_parser) + def do_disable_chv(self, opts): + """Disable PIN code using specified PIN code""" + pin = self.get_code(opts.pin_code) + (data, sw) = self._cmd.card._scc.disable_chv(opts.pin_nr, h2b(pin)) + self._cmd.poutput("CHV disable successful") - enable_chv_parser = argparse.ArgumentParser() - enable_chv_parser.add_argument('--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)') - enable_chv_parser.add_argument('pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source') + enable_chv_parser = argparse.ArgumentParser() + enable_chv_parser.add_argument( + '--pin-nr', type=int, default=1, help='PIN Number, 1=PIN1, 2=PIN2 or custom value (decimal)') + enable_chv_parser.add_argument( + 'pin_code', type=str, help='PIN code digits, \"PIN1\" or \"PIN2\" to get PIN code from external data source') - @cmd2.with_argparser(enable_chv_parser) - def do_enable_chv(self, opts): - """Enable PIN code using specified PIN code""" - pin = self.get_code(opts.pin_code) - (data, sw) = self._cmd.card._scc.enable_chv(opts.pin_nr, h2b(pin)) - self._cmd.poutput("CHV enable successful") + @cmd2.with_argparser(enable_chv_parser) + def do_enable_chv(self, opts): + """Enable PIN code using specified PIN code""" + pin = self.get_code(opts.pin_code) + (data, sw) = self._cmd.card._scc.enable_chv(opts.pin_nr, h2b(pin)) + self._cmd.poutput("CHV enable successful") - def do_deactivate_file(self, opts): - """Deactivate the current EF""" - (data, sw) = self._cmd.card._scc.deactivate_file() + def do_deactivate_file(self, opts): + """Deactivate the current EF""" + (data, sw) = self._cmd.card._scc.deactivate_file() - def do_activate_file(self, opts): - """Activate the specified EF""" - path = opts.arg_list[0] - (data, sw) = self._cmd.rs.activate_file(path) + def do_activate_file(self, opts): + """Activate the specified EF""" + path = opts.arg_list[0] + (data, sw) = self._cmd.rs.activate_file(path) - def complete_activate_file(self, text, line, begidx, endidx) -> List[str]: - """Command Line tab completion for ACTIVATE FILE""" - 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) + def complete_activate_file(self, text, line, begidx, endidx) -> List[str]: + """Command Line tab completion for ACTIVATE FILE""" + 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) - open_chan_parser = argparse.ArgumentParser() - open_chan_parser.add_argument('chan_nr', type=int, default=0, help='Channel Number') + open_chan_parser = argparse.ArgumentParser() + open_chan_parser.add_argument( + 'chan_nr', type=int, default=0, help='Channel Number') - @cmd2.with_argparser(open_chan_parser) - def do_open_channel(self, opts): - """Open a logical channel.""" - (data, sw) = self._cmd.card._scc.manage_channel(mode='open', lchan_nr=opts.chan_nr) + @cmd2.with_argparser(open_chan_parser) + def do_open_channel(self, opts): + """Open a logical channel.""" + (data, sw) = self._cmd.card._scc.manage_channel( + mode='open', lchan_nr=opts.chan_nr) - close_chan_parser = argparse.ArgumentParser() - close_chan_parser.add_argument('chan_nr', type=int, default=0, help='Channel Number') + close_chan_parser = argparse.ArgumentParser() + close_chan_parser.add_argument( + 'chan_nr', type=int, default=0, help='Channel Number') - @cmd2.with_argparser(close_chan_parser) - def do_close_channel(self, opts): - """Close a logical channel.""" - (data, sw) = self._cmd.card._scc.manage_channel(mode='close', lchan_nr=opts.chan_nr) + @cmd2.with_argparser(close_chan_parser) + def do_close_channel(self, opts): + """Close a logical channel.""" + (data, sw) = self._cmd.card._scc.manage_channel( + mode='close', lchan_nr=opts.chan_nr) - def do_status(self, opts): - """Perform the STATUS command.""" - fcp_dec = self._cmd.rs.status() - self._cmd.poutput_json(fcp_dec) + def do_status(self, opts): + """Perform the STATUS command.""" + fcp_dec = self._cmd.rs.status() + self._cmd.poutput_json(fcp_dec) - suspend_uicc_parser = argparse.ArgumentParser() - suspend_uicc_parser.add_argument('--min-duration-secs', type=int, default=60, - help='Proposed minimum duration of suspension') - suspend_uicc_parser.add_argument('--max-duration-secs', type=int, default=24*60*60, - help='Proposed maximum duration of suspension') + suspend_uicc_parser = argparse.ArgumentParser() + suspend_uicc_parser.add_argument('--min-duration-secs', type=int, default=60, + help='Proposed minimum duration of suspension') + suspend_uicc_parser.add_argument('--max-duration-secs', type=int, default=24*60*60, + help='Proposed maximum duration of suspension') - # not ISO7816-4 but TS 102 221 - @cmd2.with_argparser(suspend_uicc_parser) - def do_suspend_uicc(self, opts): - """Perform the SUSPEND UICC command. Only supported on some UICC.""" - (duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs, - max_len_secs=opts.max_duration_secs) - self._cmd.poutput('Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw)) + # not ISO7816-4 but TS 102 221 + @cmd2.with_argparser(suspend_uicc_parser) + def do_suspend_uicc(self, opts): + """Perform the SUSPEND UICC command. Only supported on some UICC.""" + (duration, token, sw) = self._cmd.card._scc.suspend_uicc(min_len_secs=opts.min_duration_secs, + max_len_secs=opts.max_duration_secs) + self._cmd.poutput( + 'Negotiated Duration: %u secs, Token: %s, SW: %s' % (duration, token, sw)) option_parser = argparse.ArgumentParser(prog='pySim-shell', description='interactive SIM card shell', @@ -778,9 +839,10 @@ argparse_add_reader_args(option_parser) global_group = option_parser.add_argument_group('General Options') global_group.add_argument('--script', metavar='PATH', default=None, help='script with pySim-shell commands to be executed automatically at start-up') -global_group.add_argument('--csv', metavar='FILE', default=None, help='Read card data from CSV file') +global_group.add_argument('--csv', metavar='FILE', + default=None, help='Read card data from CSV file') global_group.add_argument("--card_handler", dest="card_handler_config", metavar="FILE", - help="Use automatic card handling machine") + help="Use automatic card handling machine") adm_group = global_group.add_mutually_exclusive_group() adm_group.add_argument('-a', '--pin-adm', metavar='PIN_ADM1', dest='pin_adm', default=None, @@ -791,63 +853,64 @@ adm_group.add_argument('-A', '--pin-adm-hex', metavar='PIN_ADM1_HEX', dest='pin_ if __name__ == '__main__': - # Parse options - opts = option_parser.parse_args() + # Parse options + opts = option_parser.parse_args() - # If a script file is specified, be sure that it actually exists - if opts.script: - if not os.access(opts.script, os.R_OK): - print("Invalid script file!") - sys.exit(2) + # If a script file is specified, be sure that it actually exists + if opts.script: + if not os.access(opts.script, os.R_OK): + print("Invalid script file!") + sys.exit(2) - # Register csv-file as card data provider, either from specified CSV - # or from CSV file in home directory - csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv" - if opts.csv: - card_key_provider_register(CardKeyProviderCsv(opts.csv)) - if os.path.isfile(csv_default): - card_key_provider_register(CardKeyProviderCsv(csv_default)) + # Register csv-file as card data provider, either from specified CSV + # or from CSV file in home directory + csv_default = str(Path.home()) + "/.osmocom/pysim/card_data.csv" + if opts.csv: + card_key_provider_register(CardKeyProviderCsv(opts.csv)) + if os.path.isfile(csv_default): + card_key_provider_register(CardKeyProviderCsv(csv_default)) - # Init card reader driver - sl = init_reader(opts) - if sl is None: - exit(1) + # Init card reader driver + sl = init_reader(opts) + if sl is None: + exit(1) - # Create command layer - scc = SimCardCommands(transport=sl) + # Create command layer + scc = SimCardCommands(transport=sl) - # Create a card handler (for bulk provisioning) - if opts.card_handler_config: - ch = CardHandlerAuto(None, opts.card_handler_config) - else: - ch = CardHandler(sl) + # Create a card handler (for bulk provisioning) + if opts.card_handler_config: + ch = CardHandlerAuto(None, opts.card_handler_config) + else: + ch = CardHandler(sl) - # Detect and initialize the card in the reader. This may fail when there - # is no card in the reader or the card is unresponsive. PysimApp is - # able to tolerate and recover from that. - try: - rs, card = init_card(sl) - app = PysimApp(card, rs, sl, ch, opts.script) - except: - print("Card initialization failed with an exception:") - print("---------------------8<---------------------") - traceback.print_exc() - print("---------------------8<---------------------") - print("(you may still try to recover from this manually by using the 'equip' command.)") - print(" it should also be noted that some readers may behave strangely when no card") - print(" is inserted.)") - print("") - app = PysimApp(None, None, sl, ch, opts.script) + # Detect and initialize the card in the reader. This may fail when there + # is no card in the reader or the card is unresponsive. PysimApp is + # able to tolerate and recover from that. + try: + rs, card = init_card(sl) + app = PysimApp(card, rs, sl, ch, opts.script) + except: + print("Card initialization failed with an exception:") + print("---------------------8<---------------------") + traceback.print_exc() + print("---------------------8<---------------------") + print("(you may still try to recover from this manually by using the 'equip' command.)") + print( + " it should also be noted that some readers may behave strangely when no card") + print(" is inserted.)") + print("") + app = PysimApp(None, None, sl, ch, opts.script) - # If the user supplies an ADM PIN at via commandline args authenticate - # immediately so that the user does not have to use the shell commands - pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex) - if pin_adm: - if not card: - print("Card error, cannot do ADM verification with supplied ADM pin now.") - try: - card.verify_adm(h2b(pin_adm)) - except Exception as e: - print(e) + # If the user supplies an ADM PIN at via commandline args authenticate + # immediately so that the user does not have to use the shell commands + pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex) + if pin_adm: + if not card: + print("Card error, cannot do ADM verification with supplied ADM pin now.") + try: + card.verify_adm(h2b(pin_adm)) + except Exception as e: + print(e) - app.cmdloop() + app.cmdloop() diff --git a/pySim/__init__.py b/pySim/__init__.py index 8b137891..e69de29b 100644 --- a/pySim/__init__.py +++ b/pySim/__init__.py @@ -1 +0,0 @@ - diff --git a/pySim/ara_m.py b/pySim/ara_m.py index 5dee3e0b..0928f8dd 100644 --- a/pySim/ara_m.py +++ b/pySim/ara_m.py @@ -34,29 +34,35 @@ from pySim.tlv import * # various BER-TLV encoded Data Objects (DOs) + class AidRefDO(BER_TLV_IE, tag=0x4f): # SEID v1.1 Table 6-3 _construct = HexAdapter(GreedyBytes) + class AidRefEmptyDO(BER_TLV_IE, tag=0xc0): # SEID v1.1 Table 6-3 pass + class DevAppIdRefDO(BER_TLV_IE, tag=0xc1): # SEID v1.1 Table 6-4 _construct = HexAdapter(GreedyBytes) + class PkgRefDO(BER_TLV_IE, tag=0xca): # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc _construct = Struct('package_name_string'/GreedyString("ascii")) -class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO,AidRefEmptyDO,DevAppIdRefDO,PkgRefDO]): + +class RefDO(BER_TLV_IE, tag=0xe1, nested=[AidRefDO, AidRefEmptyDO, DevAppIdRefDO, PkgRefDO]): # SEID v1.1 Table 6-5 pass + class ApduArDO(BER_TLV_IE, tag=0xd0): # SEID v1.1 Table 6-8 - def _from_bytes(self, do:bytes): + def _from_bytes(self, do: bytes): if len(do) == 1: if do[0] == 0x00: self.decoded = {'generic_access_rule': 'never'} @@ -76,6 +82,7 @@ class ApduArDO(BER_TLV_IE, tag=0xd0): 'mask': b2h(do[offset+4:offset+8])} self.decoded = res return res + def _to_bytes(self): if 'generic_access_rule' in self.decoded: if self.decoded['generic_access_rule'] == 'never': @@ -99,94 +106,118 @@ class ApduArDO(BER_TLV_IE, tag=0xd0): res += header_b + mask_b return res + class NfcArDO(BER_TLV_IE, tag=0xd1): # SEID v1.1 Table 6-9 - _construct = Struct('nfc_event_access_rule'/Enum(Int8ub, never=0, always=1)) + _construct = Struct('nfc_event_access_rule' / + Enum(Int8ub, never=0, always=1)) + class PermArDO(BER_TLV_IE, tag=0xdb): # Android UICC Carrier Privileges specific extension, see https://source.android.com/devices/tech/config/uicc _construct = Struct('permissions'/HexAdapter(Bytes(8))) + class ArDO(BER_TLV_IE, tag=0xe3, nested=[ApduArDO, NfcArDO, PermArDO]): # SEID v1.1 Table 6-7 pass + class RefArDO(BER_TLV_IE, tag=0xe2, nested=[RefDO, ArDO]): # SEID v1.1 Table 6-6 pass + class ResponseAllRefArDO(BER_TLV_IE, tag=0xff40, nested=[RefArDO]): # SEID v1.1 Table 4-2 pass + class ResponseArDO(BER_TLV_IE, tag=0xff50, nested=[ArDO]): # SEID v1.1 Table 4-3 pass + class ResponseRefreshTagDO(BER_TLV_IE, tag=0xdf20): # SEID v1.1 Table 4-4 _construct = Struct('refresh_tag'/HexAdapter(Bytes(8))) + class DeviceInterfaceVersionDO(BER_TLV_IE, tag=0xe6): # SEID v1.1 Table 6-12 _construct = Struct('major'/Int8ub, 'minor'/Int8ub, 'patch'/Int8ub) + class DeviceConfigDO(BER_TLV_IE, tag=0xe4, nested=[DeviceInterfaceVersionDO]): # SEID v1.1 Table 6-10 pass + class ResponseDeviceConfigDO(BER_TLV_IE, tag=0xff7f, nested=[DeviceConfigDO]): # SEID v1.1 Table 5-14 pass + class AramConfigDO(BER_TLV_IE, tag=0xe5, nested=[DeviceInterfaceVersionDO]): # SEID v1.1 Table 6-11 pass + class ResponseAramConfigDO(BER_TLV_IE, tag=0xdf21, nested=[AramConfigDO]): # SEID v1.1 Table 4-5 pass + class CommandStoreRefArDO(BER_TLV_IE, tag=0xf0, nested=[RefArDO]): # SEID v1.1 Table 5-2 pass + class CommandDelete(BER_TLV_IE, tag=0xf1, nested=[AidRefDO, AidRefEmptyDO, RefDO, RefArDO]): # SEID v1.1 Table 5-4 pass + class CommandUpdateRefreshTagDO(BER_TLV_IE, tag=0xf2): # SEID V1.1 Table 5-6 pass + class CommandRegisterClientAidsDO(BER_TLV_IE, tag=0xf7, nested=[AidRefDO, AidRefEmptyDO]): # SEID v1.1 Table 5-7 pass + class CommandGet(BER_TLV_IE, tag=0xf3, nested=[AidRefDO, AidRefEmptyDO]): # SEID v1.1 Table 5-8 pass + class CommandGetAll(BER_TLV_IE, tag=0xf4): # SEID v1.1 Table 5-9 pass + class CommandGetClientAidsDO(BER_TLV_IE, tag=0xf6): # SEID v1.1 Table 5-10 pass + class CommandGetNext(BER_TLV_IE, tag=0xf5): # SEID v1.1 Table 5-11 pass + class CommandGetDeviceConfigDO(BER_TLV_IE, tag=0xf8): # SEID v1.1 Table 5-12 pass + class ResponseAracAidDO(BER_TLV_IE, tag=0xff70, nested=[AidRefDO, AidRefEmptyDO]): # SEID v1.1 Table 5-13 pass + class BlockDO(BER_TLV_IE, tag=0xe7): # SEID v1.1 Table 6-13 _construct = Struct('offset'/Int16ub, 'length'/Int8ub) @@ -197,11 +228,15 @@ class GetCommandDoCollection(TLV_IE_Collection, nested=[RefDO, DeviceConfigDO]): pass # SEID v1.1 Table 4-2 + + class GetResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseArDO, ResponseRefreshTagDO, ResponseAramConfigDO]): pass # SEID v1.1 Table 5-1 + + class StoreCommandDoCollection(TLV_IE_Collection, nested=[BlockDO, CommandStoreRefArDO, CommandDelete, CommandUpdateRefreshTagDO, CommandRegisterClientAidsDO, @@ -215,6 +250,7 @@ class StoreResponseDoCollection(TLV_IE_Collection, nested=[ResponseAllRefArDO, ResponseAracAidDO, ResponseDeviceConfigDO]): pass + class ADF_ARAM(CardADF): def __init__(self, aid='a00000015141434c00', name='ADF.ARA-M', fid=None, sfid=None, desc='ARA-M Application'): @@ -224,7 +260,7 @@ class ADF_ARAM(CardADF): self.add_files(files) @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 and decoding the response data tlv.""" if cmd_do: @@ -259,7 +295,8 @@ class ADF_ARAM(CardADF): @staticmethod def get_config(tp, v_major=0, v_minor=0, v_patch=1): cmd_do = DeviceConfigDO() - cmd_do.from_dict([{'DeviceInterfaceVersionDO': {'major': v_major, 'minor': v_minor, 'patch': v_patch }}]) + cmd_do.from_dict([{'DeviceInterfaceVersionDO': { + 'major': v_major, 'minor': v_minor, 'patch': v_patch}}]) return ADF_ARAM.xceive_apdu_tlv(tp, '80cadf21', cmd_do, ResponseAramConfigDO) @with_default_category('Application-Specific Commands') @@ -281,20 +318,30 @@ class ADF_ARAM(CardADF): store_ref_ar_do_parse = argparse.ArgumentParser() # REF-DO - store_ref_ar_do_parse.add_argument('--device-app-id', required=True, help='Identifies the specific device application that the rule appplies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)') + store_ref_ar_do_parse.add_argument( + '--device-app-id', required=True, help='Identifies the specific device application that the rule appplies to. Hash of Certificate of Application Provider, or UUID. (20/32 hex bytes)') aid_grp = store_ref_ar_do_parse.add_mutually_exclusive_group() - aid_grp.add_argument('--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 hex bytes)') - aid_grp.add_argument('--aid-empty', action='store_true', help='No specific SE application, applies to all applications') - store_ref_ar_do_parse.add_argument('--pkg-ref', help='Full Android Java package name (up to 127 chars ASCII)') + aid_grp.add_argument( + '--aid', help='Identifies the specific SE application for which rules are to be stored. Can be a partial AID, containing for example only the RID. (5-16 hex bytes)') + aid_grp.add_argument('--aid-empty', action='store_true', + help='No specific SE application, applies to all applications') + store_ref_ar_do_parse.add_argument( + '--pkg-ref', help='Full Android Java package name (up to 127 chars ASCII)') # AR-DO apdu_grp = store_ref_ar_do_parse.add_mutually_exclusive_group() - apdu_grp.add_argument('--apdu-never', action='store_true', help='APDU access is not allowed') - apdu_grp.add_argument('--apdu-always', action='store_true', help='APDU access is allowed') - apdu_grp.add_argument('--apdu-filter', help='APDU filter: 4 byte CLA/INS/P1/P2 followed by 4 byte mask (8 hex bytes)') + apdu_grp.add_argument( + '--apdu-never', action='store_true', help='APDU access is not allowed') + apdu_grp.add_argument( + '--apdu-always', action='store_true', help='APDU access is allowed') + apdu_grp.add_argument( + '--apdu-filter', help='APDU filter: 4 byte CLA/INS/P1/P2 followed by 4 byte mask (8 hex bytes)') nfc_grp = store_ref_ar_do_parse.add_mutually_exclusive_group() - nfc_grp.add_argument('--nfc-always', action='store_true', help='NFC event access is allowed') - nfc_grp.add_argument('--nfc-never', action='store_true', help='NFC event access is not allowed') - store_ref_ar_do_parse.add_argument('--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)') + nfc_grp.add_argument('--nfc-always', action='store_true', + help='NFC event access is allowed') + nfc_grp.add_argument('--nfc-never', action='store_true', + help='NFC event access is not allowed') + store_ref_ar_do_parse.add_argument( + '--android-permissions', help='Android UICC Carrier Privilege Permissions (8 hex bytes)') @cmd2.with_argparser(store_ref_ar_do_parse) def do_aram_store_ref_ar_do(self, opts): @@ -323,7 +370,7 @@ class ADF_ARAM(CardADF): ar_do_content += [{'NfcArDO': {'nfc_event_access_rule': 'never'}}] if 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.from_dict(d) res_do = ADF_ARAM.store_data(self._cmd.card._scc._tp, csrado) @@ -359,6 +406,7 @@ sw_aram = { } } + class CardApplicationARAM(CardApplication): def __init__(self): - super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram) + super().__init__('ARA-M', adf=ADF_ARAM(), sw=sw_aram) diff --git a/pySim/card_handler.py b/pySim/card_handler.py index 0425e33b..57e0d32b 100644 --- a/pySim/card_handler.py +++ b/pySim/card_handler.py @@ -30,116 +30,118 @@ import subprocess import sys import yaml + 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): - self.sl = sl + def __init__(self, sl: LinkBase): + self.sl = sl - def get(self, first:bool = False): - """Method called when pySim needs a new card to be inserted. + def get(self, first: bool = False): + """Method called when pySim needs a new card to be inserted. - Args: - first : set to true when the get method is called the - first time. This is required to prevent blocking - when a card is already inserted into the reader. - The reader API would not recognize that card as - "new card" until it would be removed and re-inserted - again. - """ - print("Ready for Programming: ", end='') - self._get(first) + Args: + first : set to true when the get method is called the + first time. This is required to prevent blocking + when a card is already inserted into the reader. + The reader API would not recognize that card as + "new card" until it would be removed and re-inserted + again. + """ + print("Ready for Programming: ", end='') + self._get(first) - def error(self): - """Method called when pySim failed to program a card. Move card to 'bad' batch.""" - print("Programming failed: ", end='') - self._error() + def error(self): + """Method called when pySim failed to program a card. Move card to 'bad' batch.""" + print("Programming failed: ", end='') + self._error() - def done(self): - """Method called when pySim failed to program a card. Move card to 'good' batch.""" - print("Programming successful: ", end='') - self._done() + def done(self): + """Method called when pySim failed to program a card. Move card to 'good' batch.""" + print("Programming successful: ", end='') + self._done() - def _get(self, first:bool = False): - pass + def _get(self, first: bool = False): + pass - def _error(self): - pass + def _error(self): + pass - def _done(self): - pass + def _done(self): + pass 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): - print("Insert card now (or CTRL-C to cancel)") - self.sl.wait_for_card(newcardonly=not first) + def _get(self, first: bool = False): + print("Insert card now (or CTRL-C to cancel)") + self.sl.wait_for_card(newcardonly=not first) - def _error(self): - print("Remove card from reader") - print("") + def _error(self): + print("Remove card from reader") + print("") - def _done(self): - print("Remove card from reader") - print("") + def _done(self): + print("Remove card from reader") + print("") class CardHandlerAuto(CardHandlerBase): - """Automatic card handler: A machine is used to handle the cards.""" + """Automatic card handler: A machine is used to handle the cards.""" - verbose = True + verbose = True - def __init__(self, sl:LinkBase, config_file:str): - super().__init__(sl) - print("Card handler Config-file: " + str(config_file)) - with open(config_file) as cfg: - self.cmds = yaml.load(cfg, Loader=yaml.FullLoader) - self.verbose = (self.cmds.get('verbose') == True) + def __init__(self, sl: LinkBase, config_file: str): + super().__init__(sl) + print("Card handler Config-file: " + str(config_file)) + with open(config_file) as cfg: + self.cmds = yaml.load(cfg, Loader=yaml.FullLoader) + self.verbose = (self.cmds.get('verbose') == True) - def __print_outout(self, out): - print("") - print("Card handler output:") - print("---------------------8<---------------------") - stdout = out[0].strip() - if len(stdout) > 0: - print("stdout:") - print(stdout) - stderr = out[1].strip() - if len(stderr) > 0: - print("stderr:") - print(stderr) - print("---------------------8<---------------------") - print("") + def __print_outout(self, out): + print("") + print("Card handler output:") + print("---------------------8<---------------------") + stdout = out[0].strip() + if len(stdout) > 0: + print("stdout:") + print(stdout) + stderr = out[1].strip() + if len(stderr) > 0: + print("stderr:") + print(stderr) + print("---------------------8<---------------------") + print("") - def __exec_cmd(self, command): - print("Card handler Commandline: " + str(command)) + def __exec_cmd(self, command): + print("Card handler Commandline: " + str(command)) - proc = subprocess.Popen([command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - out = proc.communicate() - rc = proc.returncode + proc = subprocess.Popen( + [command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + out = proc.communicate() + rc = proc.returncode - if rc != 0 or self.verbose: - self.__print_outout(out) + if rc != 0 or self.verbose: + self.__print_outout(out) - if rc != 0: - print("") - print("Error: Card handler failure! (rc=" + str(rc) + ")") - sys.exit(rc) + if rc != 0: + print("") + print("Error: Card handler failure! (rc=" + str(rc) + ")") + sys.exit(rc) - def _get(self, first:bool = False): - print("Transporting card into the reader-bay...") - self.__exec_cmd(self.cmds['get']) - if self.sl: - self.sl.connect() + def _get(self, first: bool = False): + print("Transporting card into the reader-bay...") + self.__exec_cmd(self.cmds['get']) + if self.sl: + self.sl.connect() - def _error(self): - print("Transporting card to the error-bin...") - self.__exec_cmd(self.cmds['error']) - print("") + def _error(self): + print("Transporting card to the error-bin...") + self.__exec_cmd(self.cmds['error']) + print("") - def _done(self): - print("Transporting card into the collector bin...") - self.__exec_cmd(self.cmds['done']) - print("") + def _done(self): + print("Transporting card into the collector bin...") + self.__exec_cmd(self.cmds['done']) + print("") diff --git a/pySim/card_key_provider.py b/pySim/card_key_provider.py index 67b0b28b..00e32aa3 100644 --- a/pySim/card_key_provider.py +++ b/pySim/card_key_provider.py @@ -33,136 +33,142 @@ from typing import List, Dict, Optional import abc import csv -card_key_providers = [] # type: List['CardKeyProvider'] +card_key_providers = [] # type: List['CardKeyProvider'] + 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 - def _verify_get_data(self, fields:List[str]=[], key:str='ICCID', value:str="") -> Dict[str,str]: - """Verify multiple fields for identified card. + # check input parameters, but do nothing concrete yet + def _verify_get_data(self, fields: List[str] = [], key: str = 'ICCID', value: str = "") -> Dict[str, str]: + """Verify multiple fields for identified card. - Args: - fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained - key : look-up key to identify card data, such as 'ICCID' - value : value for look-up key to identify card data - Returns: - dictionary of {field, value} strings for each requested field from 'fields' - """ - for f in fields: - if (f not in self.VALID_FIELD_NAMES): - raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" % - (f, str(self.VALID_FIELD_NAMES))) + Args: + fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained + key : look-up key to identify card data, such as 'ICCID' + value : value for look-up key to identify card data + Returns: + dictionary of {field, value} strings for each requested field from 'fields' + """ + for f in fields: + if (f not in self.VALID_FIELD_NAMES): + raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" % + (f, str(self.VALID_FIELD_NAMES))) - if (key not in self.VALID_FIELD_NAMES): - raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" % - (key, str(self.VALID_FIELD_NAMES))) + if (key not in self.VALID_FIELD_NAMES): + raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" % + (key, str(self.VALID_FIELD_NAMES))) - return {} + return {} - 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""" - fields = [field] - result = self.get(fields, key, value) - return result.get(field) + 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""" + fields = [field] + result = self.get(fields, key, value) + return result.get(field) - @abc.abstractmethod - def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]: - """Get multiple card-individual fields for identified card. + @abc.abstractmethod + def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]: + """Get multiple card-individual fields for identified card. + + Args: + fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained + key : look-up key to identify card data, such as 'ICCID' + value : value for look-up key to identify card data + Returns: + dictionary of {field, value} strings for each requested field from 'fields' + """ - Args: - fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained - key : look-up key to identify card data, such as 'ICCID' - value : value for look-up key to identify card data - Returns: - dictionary of {field, value} strings for each requested field from 'fields' - """ class CardKeyProviderCsv(CardKeyProvider): - """Card key provider implementation that allows to query against a specified CSV file""" - csv_file = None - filename = None + """Card key provider implementation that allows to query against a specified CSV file""" + csv_file = None + filename = None - def __init__(self, filename:str): - """ - Args: - filename : file name (path) of CSV file containing card-individual key/data - """ - self.csv_file = open(filename, 'r') - if not self.csv_file: - raise RuntimeError("Could not open CSV file '%s'" % filename) - self.filename = filename + def __init__(self, filename: str): + """ + Args: + filename : file name (path) of CSV file containing card-individual key/data + """ + self.csv_file = open(filename, 'r') + if not self.csv_file: + raise RuntimeError("Could not open CSV file '%s'" % filename) + self.filename = filename - def get(self, fields:List[str], key:str, value:str) -> Dict[str,str]: - super()._verify_get_data(fields, key, value) + def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]: + super()._verify_get_data(fields, key, value) - self.csv_file.seek(0) - cr = csv.DictReader(self.csv_file) - if not cr: - raise RuntimeError("Could not open DictReader for CSV-File '%s'" % self.filename) - cr.fieldnames = [ field.upper() for field in cr.fieldnames ] + self.csv_file.seek(0) + cr = csv.DictReader(self.csv_file) + if not cr: + raise RuntimeError( + "Could not open DictReader for CSV-File '%s'" % self.filename) + cr.fieldnames = [field.upper() for field in cr.fieldnames] - rc = {} - for row in cr: - if row[key] == value: - for f in fields: - if f in row: - rc.update({f : row[f]}) - else: - raise RuntimeError("CSV-File '%s' lacks column '%s'" % - (self.filename, f)) - return rc + rc = {} + for row in cr: + if row[key] == value: + for f in fields: + if f in row: + rc.update({f: row[f]}) + else: + raise RuntimeError("CSV-File '%s' lacks column '%s'" % + (self.filename, f)) + return rc -def card_key_provider_register(provider:CardKeyProvider, provider_list=card_key_providers): - """Register a new card key provider. +def card_key_provider_register(provider: CardKeyProvider, provider_list=card_key_providers): + """Register a new card key provider. - Args: - provider : the to-be-registered provider - provider_list : override the list of providers from the global default - """ - if not isinstance(provider, CardKeyProvider): - raise ValueError("provider is not a card data provier") - provider_list.append(provider) + Args: + provider : the to-be-registered provider + provider_list : override the list of providers from the global default + """ + if not isinstance(provider, CardKeyProvider): + raise ValueError("provider is not a card data provier") + provider_list.append(provider) -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. +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. - Args: - fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained - key : look-up key to identify card data, such as 'ICCID' - value : value for look-up key to identify card data - provider_list : override the list of providers from the global default - Returns: - dictionary of {field, value} strings for each requested field from 'fields' - """ - for p in provider_list: - if not isinstance(p, CardKeyProvider): - raise ValueError("provider list contains element which is not a card data provier") - result = p.get(fields, key, value) - if result: - return result - return {} + Args: + fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained + key : look-up key to identify card data, such as 'ICCID' + value : value for look-up key to identify card data + provider_list : override the list of providers from the global default + Returns: + dictionary of {field, value} strings for each requested field from 'fields' + """ + for p in provider_list: + if not isinstance(p, CardKeyProvider): + raise ValueError( + "provider list contains element which is not a card data provier") + result = p.get(fields, key, value) + if result: + return result + return {} -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. +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. - Args: - field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained - key : look-up key to identify card data, such as 'ICCID' - value : value for look-up key to identify card data - provider_list : override the list of providers from the global default - Returns: - dictionary of {field, value} strings for the requested field - """ - for p in provider_list: - if not isinstance(p, CardKeyProvider): - raise ValueError("provider list contains element which is not a card data provier") - result = p.get_field(field, key, value) - if result: - return result - return None + Args: + field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained + key : look-up key to identify card data, such as 'ICCID' + value : value for look-up key to identify card data + provider_list : override the list of providers from the global default + Returns: + dictionary of {field, value} strings for the requested field + """ + for p in provider_list: + if not isinstance(p, CardKeyProvider): + raise ValueError( + "provider list contains element which is not a card data provier") + result = p.get_field(field, key, value) + if result: + return result + return None diff --git a/pySim/cards.py b/pySim/cards.py index 45e44a2b..c3766e96 100644 --- a/pySim/cards.py +++ b/pySim/cards.py @@ -32,1576 +32,1614 @@ from pySim.utils import * from smartcard.util import toBytes from pytlv.TLV import * -def format_addr(addr:str, addr_type:str) -> str: - """ - helper function to format an FQDN (addr_type = '00') or IPv4 - (addr_type = '01') address string into a printable string that - contains the hexadecimal representation and the original address - string (addr) - """ - res = "" - if addr_type == '00': #FQDN - res += "\t%s # %s\n" % (s2h(addr), addr) - elif addr_type == '01': #IPv4 - octets = addr.split(".") - addr_hex = "" - for o in octets: - addr_hex += ("%02x" % int(o)) - res += "\t%s # %s\n" % (addr_hex, addr) - return res + +def format_addr(addr: str, addr_type: str) -> str: + """ + helper function to format an FQDN (addr_type = '00') or IPv4 + (addr_type = '01') address string into a printable string that + contains the hexadecimal representation and the original address + string (addr) + """ + res = "" + if addr_type == '00': # FQDN + res += "\t%s # %s\n" % (s2h(addr), addr) + elif addr_type == '01': # IPv4 + octets = addr.split(".") + addr_hex = "" + for o in octets: + addr_hex += ("%02x" % int(o)) + res += "\t%s # %s\n" % (addr_hex, addr) + return res + class SimCard(object): - name = 'SIM' + name = 'SIM' - def __init__(self, scc): - self._scc = scc - self._adm_chv_num = 4 - self._aids = [] + def __init__(self, scc): + self._scc = scc + self._adm_chv_num = 4 + self._aids = [] - def reset(self): - rc = self._scc.reset_card() - if rc is 1: - return self._scc.get_atr() - else: - return None + def reset(self): + rc = self._scc.reset_card() + if rc is 1: + return self._scc.get_atr() + else: + return None - def erase(self): - print("warning: erasing is not supported for specified card type!") - return + def erase(self): + print("warning: erasing is not supported for specified card type!") + return - def file_exists(self, fid): - res_arr = self._scc.try_select_path(fid) - for res in res_arr: - if res[1] != '9000': - return False - return True + def file_exists(self, fid): + res_arr = self._scc.try_select_path(fid) + for res in res_arr: + if res[1] != '9000': + return False + return True - def verify_adm(self, key): - """Authenticate with ADM key""" - (res, sw) = self._scc.verify_chv(self._adm_chv_num, key) - return sw + def verify_adm(self, key): + """Authenticate with ADM key""" + (res, sw) = self._scc.verify_chv(self._adm_chv_num, key) + return sw - def read_iccid(self): - (res, sw) = self._scc.read_binary(EF['ICCID']) - if sw == '9000': - return (dec_iccid(res), sw) - else: - return (None, sw) + def read_iccid(self): + (res, sw) = self._scc.read_binary(EF['ICCID']) + if sw == '9000': + return (dec_iccid(res), sw) + else: + return (None, sw) - def read_imsi(self): - (res, sw) = self._scc.read_binary(EF['IMSI']) - if sw == '9000': - return (dec_imsi(res), sw) - else: - return (None, sw) + def read_imsi(self): + (res, sw) = self._scc.read_binary(EF['IMSI']) + if sw == '9000': + return (dec_imsi(res), sw) + else: + return (None, sw) - def update_imsi(self, imsi): - data, sw = self._scc.update_binary(EF['IMSI'], enc_imsi(imsi)) - return sw + def update_imsi(self, imsi): + data, sw = self._scc.update_binary(EF['IMSI'], enc_imsi(imsi)) + return sw - def update_acc(self, acc): - data, sw = self._scc.update_binary(EF['ACC'], lpad(acc, 4, c='0')) - return sw + def update_acc(self, acc): + data, sw = self._scc.update_binary(EF['ACC'], lpad(acc, 4, c='0')) + return sw - def read_hplmn_act(self): - (res, sw) = self._scc.read_binary(EF['HPLMNAcT']) - if sw == '9000': - return (format_xplmn_w_act(res), sw) - else: - return (None, sw) + def read_hplmn_act(self): + (res, sw) = self._scc.read_binary(EF['HPLMNAcT']) + if sw == '9000': + return (format_xplmn_w_act(res), sw) + else: + return (None, sw) - def update_hplmn_act(self, mcc, mnc, access_tech='FFFF'): - """ - Update Home PLMN with access technology bit-field + def update_hplmn_act(self, mcc, mnc, access_tech='FFFF'): + """ + Update Home PLMN with access technology bit-field - See Section "10.3.37 EFHPLMNwAcT (HPLMN Selector with Access Technology)" - in ETSI TS 151 011 for the details of the access_tech field coding. - Some common values: - access_tech = '0080' # Only GSM is selected - access_tech = 'FFFF' # All technologies selected, even Reserved for Future Use ones - """ - # get size and write EF.HPLMNwAcT - data = self._scc.read_binary(EF['HPLMNwAcT'], length=None, offset=0) - size = len(data[0]) // 2 - hplmn = enc_plmn(mcc, mnc) - content = hplmn + access_tech - data, sw = self._scc.update_binary(EF['HPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) - return sw + See Section "10.3.37 EFHPLMNwAcT (HPLMN Selector with Access Technology)" + in ETSI TS 151 011 for the details of the access_tech field coding. + Some common values: + access_tech = '0080' # Only GSM is selected + access_tech = 'FFFF' # All technologies selected, even Reserved for Future Use ones + """ + # get size and write EF.HPLMNwAcT + data = self._scc.read_binary(EF['HPLMNwAcT'], length=None, offset=0) + size = len(data[0]) // 2 + hplmn = enc_plmn(mcc, mnc) + content = hplmn + access_tech + data, sw = self._scc.update_binary( + EF['HPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) + return sw - def read_oplmn_act(self): - (res, sw) = self._scc.read_binary(EF['OPLMNwAcT']) - if sw == '9000': - return (format_xplmn_w_act(res), sw) - else: - return (None, sw) + def read_oplmn_act(self): + (res, sw) = self._scc.read_binary(EF['OPLMNwAcT']) + if sw == '9000': + return (format_xplmn_w_act(res), sw) + else: + return (None, sw) - def update_oplmn_act(self, mcc, mnc, access_tech='FFFF'): - """get size and write EF.OPLMNwAcT, See note in update_hplmn_act()""" - data = self._scc.read_binary(EF['OPLMNwAcT'], length=None, offset=0) - size = len(data[0]) // 2 - hplmn = enc_plmn(mcc, mnc) - content = hplmn + access_tech - data, sw = self._scc.update_binary(EF['OPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) - return sw + def update_oplmn_act(self, mcc, mnc, access_tech='FFFF'): + """get size and write EF.OPLMNwAcT, See note in update_hplmn_act()""" + data = self._scc.read_binary(EF['OPLMNwAcT'], length=None, offset=0) + size = len(data[0]) // 2 + hplmn = enc_plmn(mcc, mnc) + content = hplmn + access_tech + data, sw = self._scc.update_binary( + EF['OPLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) + return sw - def read_plmn_act(self): - (res, sw) = self._scc.read_binary(EF['PLMNwAcT']) - if sw == '9000': - return (format_xplmn_w_act(res), sw) - else: - return (None, sw) + def read_plmn_act(self): + (res, sw) = self._scc.read_binary(EF['PLMNwAcT']) + if sw == '9000': + return (format_xplmn_w_act(res), sw) + else: + return (None, sw) - def update_plmn_act(self, mcc, mnc, access_tech='FFFF'): - """get size and write EF.PLMNwAcT, See note in update_hplmn_act()""" - data = self._scc.read_binary(EF['PLMNwAcT'], length=None, offset=0) - size = len(data[0]) // 2 - hplmn = enc_plmn(mcc, mnc) - content = hplmn + access_tech - data, sw = self._scc.update_binary(EF['PLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) - return sw + def update_plmn_act(self, mcc, mnc, access_tech='FFFF'): + """get size and write EF.PLMNwAcT, See note in update_hplmn_act()""" + data = self._scc.read_binary(EF['PLMNwAcT'], length=None, offset=0) + size = len(data[0]) // 2 + hplmn = enc_plmn(mcc, mnc) + content = hplmn + access_tech + data, sw = self._scc.update_binary( + EF['PLMNwAcT'], content + 'ffffff0000' * (size // 5 - 1)) + return sw - def update_plmnsel(self, mcc, mnc): - data = self._scc.read_binary(EF['PLMNsel'], length=None, offset=0) - size = len(data[0]) // 2 - hplmn = enc_plmn(mcc, mnc) - data, sw = self._scc.update_binary(EF['PLMNsel'], hplmn + 'ff' * (size-3)) - return sw + def update_plmnsel(self, mcc, mnc): + data = self._scc.read_binary(EF['PLMNsel'], length=None, offset=0) + size = len(data[0]) // 2 + hplmn = enc_plmn(mcc, mnc) + data, sw = self._scc.update_binary( + EF['PLMNsel'], hplmn + 'ff' * (size-3)) + return sw - def update_smsp(self, smsp): - data, sw = self._scc.update_record(EF['SMSP'], 1, rpad(smsp, 84)) - return sw + def update_smsp(self, smsp): + data, sw = self._scc.update_record(EF['SMSP'], 1, rpad(smsp, 84)) + return sw - def update_ad(self, mnc=None, opmode=None, ofm=None): - """ - Update Administrative Data (AD) + def update_ad(self, mnc=None, opmode=None, ofm=None): + """ + Update Administrative Data (AD) - See Sec. "4.2.18 EF_AD (Administrative Data)" - in 3GPP TS 31.102 for the details of the EF_AD contents. + See Sec. "4.2.18 EF_AD (Administrative Data)" + in 3GPP TS 31.102 for the details of the EF_AD contents. - Set any parameter to None to keep old value(s) on card. + Set any parameter to None to keep old value(s) on card. - Parameters: - mnc (str): MNC of IMSI - opmode (Hex-str, 1 Byte): MS Operation Mode - ofm (Hex-str, 1 Byte): Operational Feature Monitor (OFM) aka Ciphering Indicator + Parameters: + mnc (str): MNC of IMSI + opmode (Hex-str, 1 Byte): MS Operation Mode + ofm (Hex-str, 1 Byte): Operational Feature Monitor (OFM) aka Ciphering Indicator - Returns: - str: Return code of write operation - """ + Returns: + str: Return code of write operation + """ - ad = EF_AD() + ad = EF_AD() - # read from card - raw_hex_data, sw = self._scc.read_binary(EF['AD'], length=None, offset=0) - abstract_data = ad.decode_hex(raw_hex_data) + # read from card + raw_hex_data, sw = self._scc.read_binary( + EF['AD'], length=None, offset=0) + abstract_data = ad.decode_hex(raw_hex_data) - # perform updates - if mnc and abstract_data['extensions']: - mnclen = len(str(mnc)) - if mnclen == 1: - mnclen = 2 - if mnclen > 3: - raise RuntimeError('invalid length of mnc "{}"'.format(mnc)) - abstract_data['extensions']['mnc_len'] = mnclen - if opmode: - opmode_num = int(opmode, 16) - if opmode_num in [int(v) for v in EF_AD.OP_MODE]: - abstract_data['ms_operation_mode'] = opmode_num - else: - raise RuntimeError('invalid opmode "{}"'.format(opmode)) - if ofm: - abstract_data['ofm'] = bool(int(ofm, 16)) + # perform updates + if mnc and abstract_data['extensions']: + mnclen = len(str(mnc)) + if mnclen == 1: + mnclen = 2 + if mnclen > 3: + raise RuntimeError('invalid length of mnc "{}"'.format(mnc)) + abstract_data['extensions']['mnc_len'] = mnclen + if opmode: + opmode_num = int(opmode, 16) + if opmode_num in [int(v) for v in EF_AD.OP_MODE]: + abstract_data['ms_operation_mode'] = opmode_num + else: + raise RuntimeError('invalid opmode "{}"'.format(opmode)) + if ofm: + abstract_data['ofm'] = bool(int(ofm, 16)) - # write to card - raw_hex_data = ad.encode_hex(abstract_data) - data, sw = self._scc.update_binary(EF['AD'], raw_hex_data) - return sw + # write to card + raw_hex_data = ad.encode_hex(abstract_data) + data, sw = self._scc.update_binary(EF['AD'], raw_hex_data) + return sw - def read_spn(self): - (content, sw) = self._scc.read_binary(EF['SPN']) - if sw == '9000': - abstract_data = EF_SPN().decode_hex(content) - show_in_hplmn = abstract_data['show_in_hplmn'] - hide_in_oplmn = abstract_data['hide_in_oplmn'] - name = abstract_data['spn'] - return ((name, show_in_hplmn, hide_in_oplmn), sw) - else: - return (None, sw) + def read_spn(self): + (content, sw) = self._scc.read_binary(EF['SPN']) + if sw == '9000': + abstract_data = EF_SPN().decode_hex(content) + show_in_hplmn = abstract_data['show_in_hplmn'] + hide_in_oplmn = abstract_data['hide_in_oplmn'] + name = abstract_data['spn'] + return ((name, show_in_hplmn, hide_in_oplmn), sw) + else: + return (None, sw) - def update_spn(self, name="", show_in_hplmn=False, hide_in_oplmn=False): - abstract_data = { - 'hide_in_oplmn' : hide_in_oplmn, - 'show_in_hplmn' : show_in_hplmn, - 'spn' : name, - } - content = EF_SPN().encode_hex(abstract_data) - data, sw = self._scc.update_binary(EF['SPN'], content) - return sw + def update_spn(self, name="", show_in_hplmn=False, hide_in_oplmn=False): + abstract_data = { + 'hide_in_oplmn': hide_in_oplmn, + 'show_in_hplmn': show_in_hplmn, + 'spn': name, + } + content = EF_SPN().encode_hex(abstract_data) + data, sw = self._scc.update_binary(EF['SPN'], content) + return sw - def read_binary(self, ef, length=None, offset=0): - ef_path = ef in EF and EF[ef] or ef - return self._scc.read_binary(ef_path, length, offset) + def read_binary(self, ef, length=None, offset=0): + ef_path = ef in EF and EF[ef] or ef + return self._scc.read_binary(ef_path, length, offset) - def read_record(self, ef, rec_no): - ef_path = ef in EF and EF[ef] or ef - return self._scc.read_record(ef_path, rec_no) + def read_record(self, ef, rec_no): + ef_path = ef in EF and EF[ef] or ef + return self._scc.read_record(ef_path, rec_no) - def read_gid1(self): - (res, sw) = self._scc.read_binary(EF['GID1']) - if sw == '9000': - return (res, sw) - else: - return (None, sw) + def read_gid1(self): + (res, sw) = self._scc.read_binary(EF['GID1']) + if sw == '9000': + return (res, sw) + else: + return (None, sw) - def read_msisdn(self): - (res, sw) = self._scc.read_record(EF['MSISDN'], 1) - if sw == '9000': - return (dec_msisdn(res), sw) - else: - return (None, sw) + def read_msisdn(self): + (res, sw) = self._scc.read_record(EF['MSISDN'], 1) + if sw == '9000': + return (dec_msisdn(res), sw) + else: + return (None, sw) - def read_aids(self): - """Fetch all the AIDs present on UICC""" - self._aids = [] - try: - # Find out how many records the EF.DIR has - # and store all the AIDs in the UICC - rec_cnt = self._scc.record_count(EF['DIR']) - for i in range(0, rec_cnt): - rec = self._scc.read_record(EF['DIR'], i + 1) - if (rec[0][0:2], rec[0][4:6]) == ('61', '4f') and len(rec[0]) > 12 \ - and rec[0][8:8 + int(rec[0][6:8], 16) * 2] not in self._aids: - self._aids.append(rec[0][8:8 + int(rec[0][6:8], 16) * 2]) - except Exception as e: - print("Can't read AIDs from SIM -- %s" % (str(e),)) - self._aids = [] - return self._aids + def read_aids(self): + """Fetch all the AIDs present on UICC""" + self._aids = [] + try: + # Find out how many records the EF.DIR has + # and store all the AIDs in the UICC + rec_cnt = self._scc.record_count(EF['DIR']) + for i in range(0, rec_cnt): + rec = self._scc.read_record(EF['DIR'], i + 1) + if (rec[0][0:2], rec[0][4:6]) == ('61', '4f') and len(rec[0]) > 12 \ + and rec[0][8:8 + int(rec[0][6:8], 16) * 2] not in self._aids: + self._aids.append(rec[0][8:8 + int(rec[0][6:8], 16) * 2]) + except Exception as e: + print("Can't read AIDs from SIM -- %s" % (str(e),)) + self._aids = [] + return self._aids - @staticmethod - def _get_aid(adf="usim") -> str: - aid_map = {} - # First (known) halves of the U/ISIM AID - aid_map["usim"] = "a0000000871002" - aid_map["isim"] = "a0000000871004" - adf = adf.lower() - if adf in aid_map: - return aid_map[adf] - return None + @staticmethod + def _get_aid(adf="usim") -> str: + aid_map = {} + # First (known) halves of the U/ISIM AID + aid_map["usim"] = "a0000000871002" + aid_map["isim"] = "a0000000871004" + adf = adf.lower() + if adf in aid_map: + return aid_map[adf] + return None - def _complete_aid(self, aid) -> str: - """find the complete version of an ADF.U/ISIM AID""" - # Find full AID by partial AID: - if is_hex(aid): - for aid_known in self._aids: - if len(aid_known) >= len(aid) and aid == aid_known[0:len(aid)]: - return aid_known - return None + def _complete_aid(self, aid) -> str: + """find the complete version of an ADF.U/ISIM AID""" + # Find full AID by partial AID: + if is_hex(aid): + for aid_known in self._aids: + if len(aid_known) >= len(aid) and aid == aid_known[0:len(aid)]: + return aid_known + return None - def select_adf_by_aid(self, adf="usim"): - """Select ADF.U/ISIM in the Card using its full AID""" - if is_hex(adf): - aid = adf - else: - aid = self._get_aid(adf) - if aid: - aid_full = self._complete_aid(aid) - if aid_full: - return self._scc.select_adf(aid_full) - else: - # If we cannot get the full AID, try with short AID - return self._scc.select_adf(aid) - return (None, None) + def select_adf_by_aid(self, adf="usim"): + """Select ADF.U/ISIM in the Card using its full AID""" + if is_hex(adf): + aid = adf + else: + aid = self._get_aid(adf) + if aid: + aid_full = self._complete_aid(aid) + if aid_full: + return self._scc.select_adf(aid_full) + else: + # If we cannot get the full AID, try with short AID + return self._scc.select_adf(aid) + return (None, None) - def erase_binary(self, ef): - """Erase the contents of a file""" - len = self._scc.binary_size(ef) - self._scc.update_binary(ef, "ff" * len, offset=0, verify=True) + def erase_binary(self, ef): + """Erase the contents of a file""" + len = self._scc.binary_size(ef) + self._scc.update_binary(ef, "ff" * len, offset=0, verify=True) - def erase_record(self, ef, rec_no): - """Erase the contents of a single record""" - len = self._scc.record_size(ef) - self._scc.update_record(ef, rec_no, "ff" * len, force_len=False, verify=True) + def erase_record(self, ef, rec_no): + """Erase the contents of a single record""" + len = self._scc.record_size(ef) + self._scc.update_record(ef, rec_no, "ff" * len, + force_len=False, verify=True) - def set_apdu_parameter(self, cla, sel_ctrl): - """Set apdu parameters (class byte and selection control bytes)""" - self._scc.cla_byte = cla - self._scc.sel_ctrl = sel_ctrl + def set_apdu_parameter(self, cla, sel_ctrl): + """Set apdu parameters (class byte and selection control bytes)""" + self._scc.cla_byte = cla + self._scc.sel_ctrl = sel_ctrl + + def get_apdu_parameter(self): + """Get apdu parameters (class byte and selection control bytes)""" + return (self._scc.cla_byte, self._scc.sel_ctrl) - def get_apdu_parameter(self): - """Get apdu parameters (class byte and selection control bytes)""" - return (self._scc.cla_byte, self._scc.sel_ctrl) class UsimCard(SimCard): - name = 'USIM' + name = 'USIM' - def __init__(self, ssc): - super(UsimCard, self).__init__(ssc) + def __init__(self, ssc): + super(UsimCard, self).__init__(ssc) - def read_ehplmn(self): - (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['EHPLMN']) - if sw == '9000': - return (format_xplmn(res), sw) - else: - return (None, sw) + def read_ehplmn(self): + (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['EHPLMN']) + if sw == '9000': + return (format_xplmn(res), sw) + else: + return (None, sw) - def update_ehplmn(self, mcc, mnc): - data = self._scc.read_binary(EF_USIM_ADF_map['EHPLMN'], length=None, offset=0) - size = len(data[0]) // 2 - ehplmn = enc_plmn(mcc, mnc) - data, sw = self._scc.update_binary(EF_USIM_ADF_map['EHPLMN'], ehplmn) - return sw + def update_ehplmn(self, mcc, mnc): + data = self._scc.read_binary( + EF_USIM_ADF_map['EHPLMN'], length=None, offset=0) + size = len(data[0]) // 2 + ehplmn = enc_plmn(mcc, mnc) + data, sw = self._scc.update_binary(EF_USIM_ADF_map['EHPLMN'], ehplmn) + return sw - def read_epdgid(self): - (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGId']) - if sw == '9000': - try: - addr, addr_type = dec_addr_tlv(res) - except: - addr = None - addr_type = None - return (format_addr(addr, addr_type), sw) - else: - return (None, sw) + def read_epdgid(self): + (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGId']) + if sw == '9000': + try: + addr, addr_type = dec_addr_tlv(res) + except: + addr = None + addr_type = None + return (format_addr(addr, addr_type), sw) + else: + return (None, sw) - def update_epdgid(self, epdgid): - size = self._scc.binary_size(EF_USIM_ADF_map['ePDGId']) * 2 - if len(epdgid) > 0: - addr_type = get_addr_type(epdgid) - if addr_type == None: - raise ValueError("Unknown ePDG Id address type or invalid address provided") - epdgid_tlv = rpad(enc_addr_tlv(epdgid, ('%02x' % addr_type)), size) - else: - epdgid_tlv = rpad('ff', size) - data, sw = self._scc.update_binary( - EF_USIM_ADF_map['ePDGId'], epdgid_tlv) - return sw + def update_epdgid(self, epdgid): + size = self._scc.binary_size(EF_USIM_ADF_map['ePDGId']) * 2 + if len(epdgid) > 0: + addr_type = get_addr_type(epdgid) + if addr_type == None: + raise ValueError( + "Unknown ePDG Id address type or invalid address provided") + epdgid_tlv = rpad(enc_addr_tlv(epdgid, ('%02x' % addr_type)), size) + else: + epdgid_tlv = rpad('ff', size) + data, sw = self._scc.update_binary( + EF_USIM_ADF_map['ePDGId'], epdgid_tlv) + return sw - def read_ePDGSelection(self): - (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGSelection']) - if sw == '9000': - return (format_ePDGSelection(res), sw) - else: - return (None, sw) + def read_ePDGSelection(self): + (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGSelection']) + if sw == '9000': + return (format_ePDGSelection(res), sw) + else: + return (None, sw) - def update_ePDGSelection(self, mcc, mnc): - (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['ePDGSelection'], length=None, offset=0) - if sw == '9000' and (len(mcc) == 0 or len(mnc) == 0): - # Reset contents - # 80 - Tag value - (res, sw) = self._scc.update_binary(EF_USIM_ADF_map['ePDGSelection'], rpad('', len(res))) - elif sw == '9000': - (res, sw) = self._scc.update_binary(EF_USIM_ADF_map['ePDGSelection'], enc_ePDGSelection(res, mcc, mnc)) - return sw + def update_ePDGSelection(self, mcc, mnc): + (res, sw) = self._scc.read_binary( + EF_USIM_ADF_map['ePDGSelection'], length=None, offset=0) + if sw == '9000' and (len(mcc) == 0 or len(mnc) == 0): + # Reset contents + # 80 - Tag value + (res, sw) = self._scc.update_binary( + EF_USIM_ADF_map['ePDGSelection'], rpad('', len(res))) + elif sw == '9000': + (res, sw) = self._scc.update_binary( + EF_USIM_ADF_map['ePDGSelection'], enc_ePDGSelection(res, mcc, mnc)) + return sw - def read_ust(self): - (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST']) - if sw == '9000': - # Print those which are available - return ([res, dec_st(res, table="usim")], sw) - else: - return ([None, None], sw) + def read_ust(self): + (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST']) + if sw == '9000': + # Print those which are available + return ([res, dec_st(res, table="usim")], sw) + else: + return ([None, None], sw) + + def update_ust(self, service, bit=1): + (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST']) + if sw == '9000': + content = enc_st(res, service, bit) + (res, sw) = self._scc.update_binary( + EF_USIM_ADF_map['UST'], content) + return sw - def update_ust(self, service, bit=1): - (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['UST']) - if sw == '9000': - content = enc_st(res, service, bit) - (res, sw) = self._scc.update_binary(EF_USIM_ADF_map['UST'], content) - return sw class IsimCard(SimCard): - name = 'ISIM' + name = 'ISIM' - def __init__(self, ssc): - super(IsimCard, self).__init__(ssc) + def __init__(self, ssc): + super(IsimCard, self).__init__(ssc) - def read_pcscf(self): - rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['PCSCF']) - pcscf_recs = "" - for i in range(0, rec_cnt): - (res, sw) = self._scc.read_record(EF_ISIM_ADF_map['PCSCF'], i + 1) - if sw == '9000': - try: - addr, addr_type = dec_addr_tlv(res) - except: - addr = None - addr_type = None - content = format_addr(addr, addr_type) - pcscf_recs += "%s" % (len(content) and content or '\tNot available\n') - else: - pcscf_recs += "\tP-CSCF: Can't read, response code = %s\n" % (sw) - return pcscf_recs + def read_pcscf(self): + rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['PCSCF']) + pcscf_recs = "" + for i in range(0, rec_cnt): + (res, sw) = self._scc.read_record(EF_ISIM_ADF_map['PCSCF'], i + 1) + if sw == '9000': + try: + addr, addr_type = dec_addr_tlv(res) + except: + addr = None + addr_type = None + content = format_addr(addr, addr_type) + pcscf_recs += "%s" % (len(content) + and content or '\tNot available\n') + else: + pcscf_recs += "\tP-CSCF: Can't read, response code = %s\n" % ( + sw) + return pcscf_recs - def update_pcscf(self, pcscf): - if len(pcscf) > 0: - addr_type = get_addr_type(pcscf) - if addr_type == None: - raise ValueError("Unknown PCSCF address type or invalid address provided") - content = enc_addr_tlv(pcscf, ('%02x' % addr_type)) - else: - # Just the tag value - content = '80' - rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['PCSCF']) - pcscf_tlv = rpad(content, rec_size_bytes*2) - data, sw = self._scc.update_record(EF_ISIM_ADF_map['PCSCF'], 1, pcscf_tlv) - return sw + def update_pcscf(self, pcscf): + if len(pcscf) > 0: + addr_type = get_addr_type(pcscf) + if addr_type == None: + raise ValueError( + "Unknown PCSCF address type or invalid address provided") + content = enc_addr_tlv(pcscf, ('%02x' % addr_type)) + else: + # Just the tag value + content = '80' + rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['PCSCF']) + pcscf_tlv = rpad(content, rec_size_bytes*2) + data, sw = self._scc.update_record( + EF_ISIM_ADF_map['PCSCF'], 1, pcscf_tlv) + return sw - def read_domain(self): - (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['DOMAIN']) - if sw == '9000': - # Skip the inital tag value ('80') byte and get length of contents - length = int(res[2:4], 16) - content = h2s(res[4:4+(length*2)]) - return (content, sw) - else: - return (None, sw) + def read_domain(self): + (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['DOMAIN']) + if sw == '9000': + # Skip the initial tag value ('80') byte and get length of contents + length = int(res[2:4], 16) + content = h2s(res[4:4+(length*2)]) + return (content, sw) + else: + return (None, sw) - def update_domain(self, domain=None, mcc=None, mnc=None): - hex_str = "" - if domain: - hex_str = s2h(domain) - elif mcc and mnc: - # MCC and MNC always has 3 digits in domain form - plmn_str = 'mnc' + lpad(mnc, 3, "0") + '.mcc' + lpad(mcc, 3, "0") - hex_str = s2h('ims.' + plmn_str + '.3gppnetwork.org') + def update_domain(self, domain=None, mcc=None, mnc=None): + hex_str = "" + if domain: + hex_str = s2h(domain) + elif mcc and mnc: + # MCC and MNC always has 3 digits in domain form + plmn_str = 'mnc' + lpad(mnc, 3, "0") + '.mcc' + lpad(mcc, 3, "0") + hex_str = s2h('ims.' + plmn_str + '.3gppnetwork.org') - # Build TLV - tlv = TLV(['80']) - content = tlv.build({'80': hex_str}) + # Build TLV + tlv = TLV(['80']) + content = tlv.build({'80': hex_str}) - bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['DOMAIN']) - data, sw = self._scc.update_binary(EF_ISIM_ADF_map['DOMAIN'], rpad(content, bin_size_bytes*2)) - return sw + 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)) + return sw - def read_impi(self): - (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IMPI']) - if sw == '9000': - # Skip the inital tag value ('80') byte and get length of contents - length = int(res[2:4], 16) - content = h2s(res[4:4+(length*2)]) - return (content, sw) - else: - return (None, sw) + def read_impi(self): + (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IMPI']) + if sw == '9000': + # Skip the initial tag value ('80') byte and get length of contents + length = int(res[2:4], 16) + content = h2s(res[4:4+(length*2)]) + return (content, sw) + else: + return (None, sw) - def update_impi(self, impi=None): - hex_str = "" - if impi: - hex_str = s2h(impi) - # Build TLV - tlv = TLV(['80']) - content = tlv.build({'80': hex_str}) + def update_impi(self, impi=None): + hex_str = "" + if impi: + hex_str = s2h(impi) + # Build TLV + tlv = TLV(['80']) + content = tlv.build({'80': hex_str}) - bin_size_bytes = self._scc.binary_size(EF_ISIM_ADF_map['IMPI']) - data, sw = self._scc.update_binary(EF_ISIM_ADF_map['IMPI'], rpad(content, bin_size_bytes*2)) - return sw + 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)) + return sw - def read_impu(self): - rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['IMPU']) - impu_recs = "" - for i in range(0, rec_cnt): - (res, sw) = self._scc.read_record(EF_ISIM_ADF_map['IMPU'], i + 1) - if sw == '9000': - # Skip the inital tag value ('80') byte and get length of contents - length = int(res[2:4], 16) - content = h2s(res[4:4+(length*2)]) - impu_recs += "\t%s\n" % (len(content) and content or 'Not available') - else: - impu_recs += "IMS public user identity: Can't read, response code = %s\n" % (sw) - return impu_recs + def read_impu(self): + rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['IMPU']) + impu_recs = "" + for i in range(0, rec_cnt): + (res, sw) = self._scc.read_record(EF_ISIM_ADF_map['IMPU'], i + 1) + if sw == '9000': + # Skip the initial tag value ('80') byte and get length of contents + length = int(res[2:4], 16) + content = h2s(res[4:4+(length*2)]) + impu_recs += "\t%s\n" % (len(content) + and content or 'Not available') + else: + impu_recs += "IMS public user identity: Can't read, response code = %s\n" % ( + sw) + return impu_recs - def update_impu(self, impu=None): - hex_str = "" - if impu: - hex_str = s2h(impu) - # Build TLV - tlv = TLV(['80']) - content = tlv.build({'80': hex_str}) + def update_impu(self, impu=None): + hex_str = "" + if impu: + hex_str = s2h(impu) + # Build TLV + tlv = TLV(['80']) + content = tlv.build({'80': hex_str}) - rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['IMPU']) - impu_tlv = rpad(content, rec_size_bytes*2) - data, sw = self._scc.update_record(EF_ISIM_ADF_map['IMPU'], 1, impu_tlv) - return sw + rec_size_bytes = self._scc.record_size(EF_ISIM_ADF_map['IMPU']) + impu_tlv = rpad(content, rec_size_bytes*2) + data, sw = self._scc.update_record( + EF_ISIM_ADF_map['IMPU'], 1, impu_tlv) + return sw + + def read_iari(self): + rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['UICCIARI']) + uiari_recs = "" + for i in range(0, rec_cnt): + (res, sw) = self._scc.read_record( + EF_ISIM_ADF_map['UICCIARI'], i + 1) + if sw == '9000': + # Skip the initial tag value ('80') byte and get length of contents + length = int(res[2:4], 16) + content = h2s(res[4:4+(length*2)]) + uiari_recs += "\t%s\n" % (len(content) + and content or 'Not available') + else: + uiari_recs += "UICC IARI: Can't read, response code = %s\n" % ( + sw) + return uiari_recs - def read_iari(self): - rec_cnt = self._scc.record_count(EF_ISIM_ADF_map['UICCIARI']) - uiari_recs = "" - for i in range(0, rec_cnt): - (res, sw) = self._scc.read_record(EF_ISIM_ADF_map['UICCIARI'], i + 1) - if sw == '9000': - # Skip the inital tag value ('80') byte and get length of contents - length = int(res[2:4], 16) - content = h2s(res[4:4+(length*2)]) - uiari_recs += "\t%s\n" % (len(content) and content or 'Not available') - else: - uiari_recs += "UICC IARI: Can't read, response code = %s\n" % (sw) - return uiari_recs class MagicSimBase(abc.ABC, SimCard): - """ - Theses cards uses several record based EFs to store the provider infos, - each possible provider uses a specific record number in each EF. The - indexes used are ( where N is the number of providers supported ) : - - [2 .. N+1] for the operator name - - [1 .. N] for the programmable EFs + """ + Theses cards uses several record based EFs to store the provider infos, + each possible provider uses a specific record number in each EF. The + indexes used are ( where N is the number of providers supported ) : + - [2 .. N+1] for the operator name + - [1 .. N] for the programmable EFs - * 3f00/7f4d/8f0c : Operator Name + * 3f00/7f4d/8f0c : Operator Name - bytes 0-15 : provider name, padded with 0xff - byte 16 : length of the provider name - byte 17 : 01 for valid records, 00 otherwise + bytes 0-15 : provider name, padded with 0xff + byte 16 : length of the provider name + byte 17 : 01 for valid records, 00 otherwise - * 3f00/7f4d/8f0d : Programmable Binary EFs + * 3f00/7f4d/8f0d : Programmable Binary EFs - * 3f00/7f4d/8f0e : Programmable Record EFs + * 3f00/7f4d/8f0e : Programmable Record EFs - """ + """ - _files = { } # type: Dict[str, Tuple[str, int, bool]] - _ki_file = None # type: Optional[str] + _files = {} # type: Dict[str, Tuple[str, int, bool]] + _ki_file = None # type: Optional[str] - @classmethod - def autodetect(kls, scc): - try: - for p, l, t in kls._files.values(): - if not t: - continue - if scc.record_size(['3f00', '7f4d', p]) != l: - return None - except: - return None + @classmethod + def autodetect(kls, scc): + try: + for p, l, t in kls._files.values(): + if not t: + continue + if scc.record_size(['3f00', '7f4d', p]) != l: + return None + except: + return None - return kls(scc) + return kls(scc) - def _get_count(self): - """ - Selects the file and returns the total number of entries - and entry size - """ - f = self._files['name'] + def _get_count(self): + """ + Selects the file and returns the total number of entries + and entry size + """ + f = self._files['name'] - r = self._scc.select_path(['3f00', '7f4d', f[0]]) - rec_len = int(r[-1][28:30], 16) - tlen = int(r[-1][4:8],16) - rec_cnt = (tlen // rec_len) - 1 + r = self._scc.select_path(['3f00', '7f4d', f[0]]) + rec_len = int(r[-1][28:30], 16) + tlen = int(r[-1][4:8], 16) + rec_cnt = (tlen // rec_len) - 1 - if (rec_cnt < 1) or (rec_len != f[1]): - raise RuntimeError('Bad card type') + if (rec_cnt < 1) or (rec_len != f[1]): + raise RuntimeError('Bad card type') - return rec_cnt + return rec_cnt - def program(self, p): - # Go to dir - self._scc.select_path(['3f00', '7f4d']) + def program(self, p): + # Go to dir + self._scc.select_path(['3f00', '7f4d']) - # Home PLMN in PLMN_Sel format - hplmn = enc_plmn(p['mcc'], p['mnc']) + # Home PLMN in PLMN_Sel format + hplmn = enc_plmn(p['mcc'], p['mnc']) - # Operator name ( 3f00/7f4d/8f0c ) - self._scc.update_record(self._files['name'][0], 2, - rpad(b2h(p['name']), 32) + ('%02x' % len(p['name'])) + '01' - ) + # Operator name ( 3f00/7f4d/8f0c ) + self._scc.update_record(self._files['name'][0], 2, + rpad(b2h(p['name']), 32) + ('%02x' % + len(p['name'])) + '01' + ) - # ICCID/IMSI/Ki/HPLMN ( 3f00/7f4d/8f0d ) - v = '' + # ICCID/IMSI/Ki/HPLMN ( 3f00/7f4d/8f0d ) + v = '' - # inline Ki - if self._ki_file is None: - v += p['ki'] + # inline Ki + if self._ki_file is None: + v += p['ki'] - # ICCID - v += '3f00' + '2fe2' + '0a' + enc_iccid(p['iccid']) + # ICCID + v += '3f00' + '2fe2' + '0a' + enc_iccid(p['iccid']) - # IMSI - v += '7f20' + '6f07' + '09' + enc_imsi(p['imsi']) + # IMSI + v += '7f20' + '6f07' + '09' + enc_imsi(p['imsi']) - # Ki - if self._ki_file: - v += self._ki_file + '10' + p['ki'] + # Ki + if self._ki_file: + v += self._ki_file + '10' + p['ki'] - # PLMN_Sel - v+= '6f30' + '18' + rpad(hplmn, 36) + # PLMN_Sel + v += '6f30' + '18' + rpad(hplmn, 36) - # ACC - # This doesn't work with "fake" SuperSIM cards, - # but will hopefully work with real SuperSIMs. - if p.get('acc') is not None: - v+= '6f78' + '02' + lpad(p['acc'], 4) + # ACC + # This doesn't work with "fake" SuperSIM cards, + # but will hopefully work with real SuperSIMs. + if p.get('acc') is not None: + v += '6f78' + '02' + lpad(p['acc'], 4) - self._scc.update_record(self._files['b_ef'][0], 1, - rpad(v, self._files['b_ef'][1]*2) - ) + self._scc.update_record(self._files['b_ef'][0], 1, + rpad(v, self._files['b_ef'][1]*2) + ) - # SMSP ( 3f00/7f4d/8f0e ) - # FIXME + # SMSP ( 3f00/7f4d/8f0e ) + # FIXME - # Write PLMN_Sel forcefully as well - r = self._scc.select_path(['3f00', '7f20', '6f30']) - tl = int(r[-1][4:8], 16) + # Write PLMN_Sel forcefully as well + r = self._scc.select_path(['3f00', '7f20', '6f30']) + tl = int(r[-1][4:8], 16) - hplmn = enc_plmn(p['mcc'], p['mnc']) - self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3)) + hplmn = enc_plmn(p['mcc'], p['mnc']) + self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3)) - def erase(self): - # Dummy - df = {} - for k, v in self._files.items(): - ofs = 1 - fv = v[1] * 'ff' - if k == 'name': - ofs = 2 - fv = fv[0:-4] + '0000' - df[v[0]] = (fv, ofs) + def erase(self): + # Dummy + df = {} + for k, v in self._files.items(): + ofs = 1 + fv = v[1] * 'ff' + if k == 'name': + ofs = 2 + fv = fv[0:-4] + '0000' + df[v[0]] = (fv, ofs) - # Write - for n in range(0,self._get_count()): - for k, (msg, ofs) in df.items(): - self._scc.update_record(['3f00', '7f4d', k], n + ofs, msg) + # Write + for n in range(0, self._get_count()): + for k, (msg, ofs) in df.items(): + self._scc.update_record(['3f00', '7f4d', k], n + ofs, msg) class SuperSim(MagicSimBase): - name = 'supersim' + name = 'supersim' - _files = { - 'name' : ('8f0c', 18, True), - 'b_ef' : ('8f0d', 74, True), - 'r_ef' : ('8f0e', 50, True), - } + _files = { + 'name': ('8f0c', 18, True), + 'b_ef': ('8f0d', 74, True), + 'r_ef': ('8f0e', 50, True), + } - _ki_file = None + _ki_file = None class MagicSim(MagicSimBase): - name = 'magicsim' + name = 'magicsim' - _files = { - 'name' : ('8f0c', 18, True), - 'b_ef' : ('8f0d', 130, True), - 'r_ef' : ('8f0e', 102, False), - } + _files = { + 'name': ('8f0c', 18, True), + 'b_ef': ('8f0d', 130, True), + 'r_ef': ('8f0e', 102, False), + } - _ki_file = '6f1b' + _ki_file = '6f1b' class FakeMagicSim(SimCard): - """ - Theses cards have a record based EF 3f00/000c that contains the provider - information. See the program method for its format. The records go from - 1 to N. - """ + """ + Theses cards have a record based EF 3f00/000c that contains the provider + information. See the program method for its format. The records go from + 1 to N. + """ - name = 'fakemagicsim' + name = 'fakemagicsim' - @classmethod - def autodetect(kls, scc): - try: - if scc.record_size(['3f00', '000c']) != 0x5a: - return None - except: - return None + @classmethod + def autodetect(kls, scc): + try: + if scc.record_size(['3f00', '000c']) != 0x5a: + return None + except: + return None - return kls(scc) + return kls(scc) - def _get_infos(self): - """ - Selects the file and returns the total number of entries - and entry size - """ + def _get_infos(self): + """ + Selects the file and returns the total number of entries + and entry size + """ - r = self._scc.select_path(['3f00', '000c']) - rec_len = int(r[-1][28:30], 16) - tlen = int(r[-1][4:8],16) - rec_cnt = (tlen // rec_len) - 1 + r = self._scc.select_path(['3f00', '000c']) + rec_len = int(r[-1][28:30], 16) + tlen = int(r[-1][4:8], 16) + rec_cnt = (tlen // rec_len) - 1 - if (rec_cnt < 1) or (rec_len != 0x5a): - raise RuntimeError('Bad card type') + if (rec_cnt < 1) or (rec_len != 0x5a): + raise RuntimeError('Bad card type') - return rec_cnt, rec_len + return rec_cnt, rec_len - def program(self, p): - # Home PLMN - r = self._scc.select_path(['3f00', '7f20', '6f30']) - tl = int(r[-1][4:8], 16) + def program(self, p): + # Home PLMN + r = self._scc.select_path(['3f00', '7f20', '6f30']) + tl = int(r[-1][4:8], 16) - hplmn = enc_plmn(p['mcc'], p['mnc']) - self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3)) + hplmn = enc_plmn(p['mcc'], p['mnc']) + self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3)) - # Get total number of entries and entry size - rec_cnt, rec_len = self._get_infos() + # Get total number of entries and entry size + rec_cnt, rec_len = self._get_infos() - # Set first entry - entry = ( - '81' + # 1b Status: Valid & Active - rpad(s2h(p['name'][0:14]), 28) + # 14b Entry Name - enc_iccid(p['iccid']) + # 10b ICCID - enc_imsi(p['imsi']) + # 9b IMSI_len + id_type(9) + IMSI - p['ki'] + # 16b Ki - lpad(p['smsp'], 80) # 40b SMSP (padded with ff if needed) - ) - self._scc.update_record('000c', 1, entry) + # Set first entry + entry = ( + '81' + # 1b Status: Valid & Active + rpad(s2h(p['name'][0:14]), 28) + # 14b Entry Name + enc_iccid(p['iccid']) + # 10b ICCID + enc_imsi(p['imsi']) + # 9b IMSI_len + id_type(9) + IMSI + p['ki'] + # 16b Ki + lpad(p['smsp'], 80) # 40b SMSP (padded with ff if needed) + ) + self._scc.update_record('000c', 1, entry) - def erase(self): - # Get total number of entries and entry size - rec_cnt, rec_len = self._get_infos() + def erase(self): + # Get total number of entries and entry size + rec_cnt, rec_len = self._get_infos() - # Erase all entries - entry = 'ff' * rec_len - for i in range(0, rec_cnt): - self._scc.update_record('000c', 1+i, entry) + # Erase all entries + entry = 'ff' * rec_len + for i in range(0, rec_cnt): + self._scc.update_record('000c', 1+i, entry) class GrcardSim(SimCard): - """ - Greencard (grcard.cn) HZCOS GSM SIM - These cards have a much more regular ISO 7816-4 / TS 11.11 structure, - and use standard UPDATE RECORD / UPDATE BINARY commands except for Ki. - """ + """ + Greencard (grcard.cn) HZCOS GSM SIM + These cards have a much more regular ISO 7816-4 / TS 11.11 structure, + and use standard UPDATE RECORD / UPDATE BINARY commands except for Ki. + """ - name = 'grcardsim' + name = 'grcardsim' - @classmethod - def autodetect(kls, scc): - return None + @classmethod + def autodetect(kls, scc): + return None - def program(self, p): - # We don't really know yet what ADM PIN 4 is about - #self._scc.verify_chv(4, h2b("4444444444444444")) + def program(self, p): + # We don't really know yet what ADM PIN 4 is about + #self._scc.verify_chv(4, h2b("4444444444444444")) - # Authenticate using ADM PIN 5 - if p['pin_adm']: - pin = h2b(p['pin_adm']) - else: - pin = h2b("4444444444444444") - self._scc.verify_chv(5, pin) + # Authenticate using ADM PIN 5 + if p['pin_adm']: + pin = h2b(p['pin_adm']) + else: + pin = h2b("4444444444444444") + self._scc.verify_chv(5, pin) - # EF.ICCID - r = self._scc.select_path(['3f00', '2fe2']) - data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) + # EF.ICCID + r = self._scc.select_path(['3f00', '2fe2']) + data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) - # EF.IMSI - r = self._scc.select_path(['3f00', '7f20', '6f07']) - data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) + # EF.IMSI + r = self._scc.select_path(['3f00', '7f20', '6f07']) + data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) - # EF.ACC - if p.get('acc') is not None: - data, sw = self._scc.update_binary('6f78', lpad(p['acc'], 4)) + # EF.ACC + if p.get('acc') is not None: + data, sw = self._scc.update_binary('6f78', lpad(p['acc'], 4)) - # EF.SMSP - if p.get('smsp'): - r = self._scc.select_path(['3f00', '7f10', '6f42']) - data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80)) + # EF.SMSP + if p.get('smsp'): + r = self._scc.select_path(['3f00', '7f10', '6f42']) + data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80)) - # Set the Ki using proprietary command - pdu = '80d4020010' + p['ki'] - data, sw = self._scc._tp.send_apdu(pdu) + # Set the Ki using proprietary command + pdu = '80d4020010' + p['ki'] + data, sw = self._scc._tp.send_apdu(pdu) - # EF.HPLMN - r = self._scc.select_path(['3f00', '7f20', '6f30']) - size = int(r[-1][4:8], 16) - hplmn = enc_plmn(p['mcc'], p['mnc']) - self._scc.update_binary('6f30', hplmn + 'ff' * (size-3)) + # EF.HPLMN + r = self._scc.select_path(['3f00', '7f20', '6f30']) + size = int(r[-1][4:8], 16) + hplmn = enc_plmn(p['mcc'], p['mnc']) + self._scc.update_binary('6f30', hplmn + 'ff' * (size-3)) - # EF.SPN (Service Provider Name) - r = self._scc.select_path(['3f00', '7f20', '6f30']) - size = int(r[-1][4:8], 16) - # FIXME + # EF.SPN (Service Provider Name) + r = self._scc.select_path(['3f00', '7f20', '6f30']) + size = int(r[-1][4:8], 16) + # FIXME - # FIXME: EF.MSISDN + # FIXME: EF.MSISDN class SysmoSIMgr1(GrcardSim): - """ - sysmocom sysmoSIM-GR1 - These cards have a much more regular ISO 7816-4 / TS 11.11 structure, - and use standard UPDATE RECORD / UPDATE BINARY commands except for Ki. - """ - name = 'sysmosim-gr1' + """ + sysmocom sysmoSIM-GR1 + These cards have a much more regular ISO 7816-4 / TS 11.11 structure, + and use standard UPDATE RECORD / UPDATE BINARY commands except for Ki. + """ + name = 'sysmosim-gr1' + + @classmethod + def autodetect(kls, scc): + try: + # Look for ATR + if scc.get_atr() == toBytes("3B 99 18 00 11 88 22 33 44 55 66 77 60"): + return kls(scc) + except: + return None + return None - @classmethod - def autodetect(kls, scc): - try: - # Look for ATR - if scc.get_atr() == toBytes("3B 99 18 00 11 88 22 33 44 55 66 77 60"): - return kls(scc) - except: - return None - return None class SysmoUSIMgr1(UsimCard): - """ - sysmocom sysmoUSIM-GR1 - """ - name = 'sysmoUSIM-GR1' + """ + sysmocom sysmoUSIM-GR1 + """ + name = 'sysmoUSIM-GR1' - @classmethod - def autodetect(kls, scc): - # TODO: Access the ATR - return None + @classmethod + def autodetect(kls, scc): + # TODO: Access the ATR + return None - def program(self, p): - # TODO: check if verify_chv could be used or what it needs - # self._scc.verify_chv(0x0A, [0x33,0x32,0x32,0x31,0x33,0x32,0x33,0x32]) - # Unlock the card.. - data, sw = self._scc._tp.send_apdu_checksw("0020000A083332323133323332") + def program(self, p): + # TODO: check if verify_chv could be used or what it needs + # self._scc.verify_chv(0x0A, [0x33,0x32,0x32,0x31,0x33,0x32,0x33,0x32]) + # Unlock the card.. + data, sw = self._scc._tp.send_apdu_checksw( + "0020000A083332323133323332") - # TODO: move into SimCardCommands - par = ( p['ki'] + # 16b K - p['opc'] + # 32b OPC - enc_iccid(p['iccid']) + # 10b ICCID - enc_imsi(p['imsi']) # 9b IMSI_len + id_type(9) + IMSI - ) - data, sw = self._scc._tp.send_apdu_checksw("0099000033" + par) + # TODO: move into SimCardCommands + par = (p['ki'] + # 16b K + p['opc'] + # 32b OPC + enc_iccid(p['iccid']) + # 10b ICCID + enc_imsi(p['imsi']) # 9b IMSI_len + id_type(9) + IMSI + ) + data, sw = self._scc._tp.send_apdu_checksw("0099000033" + par) class SysmoSIMgr2(SimCard): - """ - sysmocom sysmoSIM-GR2 - """ + """ + sysmocom sysmoSIM-GR2 + """ - name = 'sysmoSIM-GR2' + name = 'sysmoSIM-GR2' - @classmethod - def autodetect(kls, scc): - try: - # Look for ATR - if scc.get_atr() == toBytes("3B 7D 94 00 00 55 55 53 0A 74 86 93 0B 24 7C 4D 54 68"): - return kls(scc) - except: - return None - return None + @classmethod + def autodetect(kls, scc): + try: + # Look for ATR + if scc.get_atr() == toBytes("3B 7D 94 00 00 55 55 53 0A 74 86 93 0B 24 7C 4D 54 68"): + return kls(scc) + except: + return None + return None - def program(self, p): + def program(self, p): - # select MF - r = self._scc.select_path(['3f00']) + # select MF + r = self._scc.select_path(['3f00']) - # authenticate as SUPER ADM using default key - self._scc.verify_chv(0x0b, h2b("3838383838383838")) + # authenticate as SUPER ADM using default key + self._scc.verify_chv(0x0b, h2b("3838383838383838")) - # set ADM pin using proprietary command - # INS: D4 - # P1: 3A for PIN, 3B for PUK - # P2: CHV number, as in VERIFY CHV for PIN, and as in UNBLOCK CHV for PUK - # P3: 08, CHV length (curiously the PUK is also 08 length, instead of 10) - if p['pin_adm']: - pin = h2b(p['pin_adm']) - else: - pin = h2b("4444444444444444") + # set ADM pin using proprietary command + # INS: D4 + # P1: 3A for PIN, 3B for PUK + # P2: CHV number, as in VERIFY CHV for PIN, and as in UNBLOCK CHV for PUK + # P3: 08, CHV length (curiously the PUK is also 08 length, instead of 10) + if p['pin_adm']: + pin = h2b(p['pin_adm']) + else: + pin = h2b("4444444444444444") - pdu = 'A0D43A0508' + b2h(pin) - data, sw = self._scc._tp.send_apdu(pdu) + pdu = 'A0D43A0508' + b2h(pin) + data, sw = self._scc._tp.send_apdu(pdu) - # authenticate as ADM (enough to write file, and can set PINs) + # authenticate as ADM (enough to write file, and can set PINs) - self._scc.verify_chv(0x05, pin) + self._scc.verify_chv(0x05, pin) - # write EF.ICCID - data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) + # write EF.ICCID + data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) - # select DF_GSM - r = self._scc.select_path(['7f20']) + # select DF_GSM + r = self._scc.select_path(['7f20']) - # write EF.IMSI - data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) + # write EF.IMSI + data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) - # write EF.ACC - if p.get('acc') is not None: - data, sw = self._scc.update_binary('6f78', lpad(p['acc'], 4)) + # write EF.ACC + if p.get('acc') is not None: + data, sw = self._scc.update_binary('6f78', lpad(p['acc'], 4)) - # get size and write EF.HPLMN - r = self._scc.select_path(['6f30']) - size = int(r[-1][4:8], 16) - hplmn = enc_plmn(p['mcc'], p['mnc']) - self._scc.update_binary('6f30', hplmn + 'ff' * (size-3)) + # get size and write EF.HPLMN + r = self._scc.select_path(['6f30']) + size = int(r[-1][4:8], 16) + hplmn = enc_plmn(p['mcc'], p['mnc']) + self._scc.update_binary('6f30', hplmn + 'ff' * (size-3)) - # set COMP128 version 0 in proprietary file - data, sw = self._scc.update_binary('0001', '001000') + # set COMP128 version 0 in proprietary file + data, sw = self._scc.update_binary('0001', '001000') - # set Ki in proprietary file - data, sw = self._scc.update_binary('0001', p['ki'], 3) + # set Ki in proprietary file + data, sw = self._scc.update_binary('0001', p['ki'], 3) - # select DF_TELECOM - r = self._scc.select_path(['3f00', '7f10']) + # select DF_TELECOM + r = self._scc.select_path(['3f00', '7f10']) - # write EF.SMSP - if p.get('smsp'): - data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80)) + # write EF.SMSP + if p.get('smsp'): + data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80)) class SysmoUSIMSJS1(UsimCard): - """ - sysmocom sysmoUSIM-SJS1 - """ + """ + sysmocom sysmoUSIM-SJS1 + """ - name = 'sysmoUSIM-SJS1' + name = 'sysmoUSIM-SJS1' - def __init__(self, ssc): - super(SysmoUSIMSJS1, self).__init__(ssc) - self._scc.cla_byte = "00" - self._scc.sel_ctrl = "0004" #request an FCP + def __init__(self, ssc): + super(SysmoUSIMSJS1, self).__init__(ssc) + self._scc.cla_byte = "00" + self._scc.sel_ctrl = "0004" # request an FCP - @classmethod - def autodetect(kls, scc): - try: - # Look for ATR - if scc.get_atr() == toBytes("3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 43 20 07 18 00 00 01 A5"): - return kls(scc) - except: - return None - return None + @classmethod + def autodetect(kls, scc): + try: + # Look for ATR + if scc.get_atr() == toBytes("3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 43 20 07 18 00 00 01 A5"): + return kls(scc) + except: + return None + return None - def verify_adm(self, key): - # authenticate as ADM using default key (written on the card..) - if not key: - raise ValueError("Please provide a PIN-ADM as there is no default one") - (res, sw) = self._scc.verify_chv(0x0A, key) - return sw + def verify_adm(self, key): + # authenticate as ADM using default key (written on the card..) + if not key: + raise ValueError( + "Please provide a PIN-ADM as there is no default one") + (res, sw) = self._scc.verify_chv(0x0A, key) + return sw - def program(self, p): - self.verify_adm(h2b(p['pin_adm'])) + def program(self, p): + self.verify_adm(h2b(p['pin_adm'])) - # select MF - r = self._scc.select_path(['3f00']) + # select MF + r = self._scc.select_path(['3f00']) - # write EF.ICCID - data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) + # write EF.ICCID + data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) - # select DF_GSM - r = self._scc.select_path(['7f20']) + # select DF_GSM + r = self._scc.select_path(['7f20']) - # set Ki in proprietary file - data, sw = self._scc.update_binary('00FF', p['ki']) + # set Ki in proprietary file + data, sw = self._scc.update_binary('00FF', p['ki']) - # set OPc in proprietary file - if 'opc' in p: - content = "01" + p['opc'] - data, sw = self._scc.update_binary('00F7', content) + # set OPc in proprietary file + if 'opc' in p: + content = "01" + p['opc'] + data, sw = self._scc.update_binary('00F7', content) - # set Service Provider Name - if p.get('name') is not None: - self.update_spn(p['name'], True, True) + # set Service Provider Name + if p.get('name') is not None: + self.update_spn(p['name'], True, True) - if p.get('acc') is not None: - self.update_acc(p['acc']) + if p.get('acc') is not None: + self.update_acc(p['acc']) - # write EF.IMSI - data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) + # write EF.IMSI + data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) - # EF.PLMNsel - if p.get('mcc') and p.get('mnc'): - sw = self.update_plmnsel(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming PLMNsel failed with code %s"%sw) + # EF.PLMNsel + if p.get('mcc') and p.get('mnc'): + sw = self.update_plmnsel(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming PLMNsel failed with code %s" % sw) - # EF.PLMNwAcT - if p.get('mcc') and p.get('mnc'): - sw = self.update_plmn_act(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming PLMNwAcT failed with code %s"%sw) + # EF.PLMNwAcT + if p.get('mcc') and p.get('mnc'): + sw = self.update_plmn_act(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming PLMNwAcT failed with code %s" % sw) - # EF.OPLMNwAcT - if p.get('mcc') and p.get('mnc'): - sw = self.update_oplmn_act(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming OPLMNwAcT failed with code %s"%sw) + # EF.OPLMNwAcT + if p.get('mcc') and p.get('mnc'): + sw = self.update_oplmn_act(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming OPLMNwAcT failed with code %s" % sw) - # EF.HPLMNwAcT - if p.get('mcc') and p.get('mnc'): - sw = self.update_hplmn_act(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming HPLMNwAcT failed with code %s"%sw) + # EF.HPLMNwAcT + if p.get('mcc') and p.get('mnc'): + sw = self.update_hplmn_act(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming HPLMNwAcT failed with code %s" % sw) - # EF.AD - if (p.get('mcc') and p.get('mnc')) or p.get('opmode'): - if p.get('mcc') and p.get('mnc'): - mnc = p['mnc'] - else: - mnc = None - sw = self.update_ad(mnc=mnc, opmode=p.get('opmode')) - if sw != '9000': - print("Programming AD failed with code %s"%sw) + # EF.AD + if (p.get('mcc') and p.get('mnc')) or p.get('opmode'): + if p.get('mcc') and p.get('mnc'): + mnc = p['mnc'] + else: + mnc = None + sw = self.update_ad(mnc=mnc, opmode=p.get('opmode')) + if sw != '9000': + print("Programming AD failed with code %s" % sw) - # EF.SMSP - if p.get('smsp'): - r = self._scc.select_path(['3f00', '7f10']) - data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True) + # EF.SMSP + if p.get('smsp'): + r = self._scc.select_path(['3f00', '7f10']) + data, sw = self._scc.update_record( + '6f42', 1, lpad(p['smsp'], 104), force_len=True) - # EF.MSISDN - # TODO: Alpha Identifier (currently 'ff'O * 20) - # TODO: Capability/Configuration1 Record Identifier - # TODO: Extension1 Record Identifier - if p.get('msisdn') is not None: - msisdn = enc_msisdn(p['msisdn']) - data = 'ff' * 20 + msisdn + # EF.MSISDN + # TODO: Alpha Identifier (currently 'ff'O * 20) + # TODO: Capability/Configuration1 Record Identifier + # TODO: Extension1 Record Identifier + if p.get('msisdn') is not None: + msisdn = enc_msisdn(p['msisdn']) + data = 'ff' * 20 + msisdn - r = self._scc.select_path(['3f00', '7f10']) - data, sw = self._scc.update_record('6F40', 1, data, force_len=True) + r = self._scc.select_path(['3f00', '7f10']) + data, sw = self._scc.update_record('6F40', 1, data, force_len=True) class FairwavesSIM(UsimCard): - """ - FairwavesSIM + """ + FairwavesSIM - The SIM card is operating according to the standard. - For Ki/OP/OPC programming the following files are additionally open for writing: - 3F00/7F20/FF01 – OP/OPC: - byte 1 = 0x01, bytes 2-17: OPC; - byte 1 = 0x00, bytes 2-17: OP; - 3F00/7F20/FF02: Ki - """ + The SIM card is operating according to the standard. + For Ki/OP/OPC programming the following files are additionally open for writing: + 3F00/7F20/FF01 – OP/OPC: + byte 1 = 0x01, bytes 2-17: OPC; + byte 1 = 0x00, bytes 2-17: OP; + 3F00/7F20/FF02: Ki + """ - name = 'Fairwaves-SIM' - # Propriatary files - _EF_num = { - 'Ki': 'FF02', - 'OP/OPC': 'FF01', - } - _EF = { - 'Ki': DF['GSM']+[_EF_num['Ki']], - 'OP/OPC': DF['GSM']+[_EF_num['OP/OPC']], - } + name = 'Fairwaves-SIM' + # Propriatary files + _EF_num = { + 'Ki': 'FF02', + 'OP/OPC': 'FF01', + } + _EF = { + 'Ki': DF['GSM']+[_EF_num['Ki']], + 'OP/OPC': DF['GSM']+[_EF_num['OP/OPC']], + } - def __init__(self, ssc): - super(FairwavesSIM, self).__init__(ssc) - self._adm_chv_num = 0x11 - self._adm2_chv_num = 0x12 + def __init__(self, ssc): + super(FairwavesSIM, self).__init__(ssc) + self._adm_chv_num = 0x11 + self._adm2_chv_num = 0x12 + @classmethod + def autodetect(kls, scc): + try: + # Look for ATR + if scc.get_atr() == toBytes("3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 44 22 06 10 00 00 01 A9"): + return kls(scc) + except: + return None + return None - @classmethod - def autodetect(kls, scc): - try: - # Look for ATR - if scc.get_atr() == toBytes("3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 44 22 06 10 00 00 01 A9"): - return kls(scc) - except: - return None - return None + def verify_adm2(self, key): + ''' + Authenticate with ADM2 key. + Fairwaves SIM cards support hierarchical key structure and ADM2 key + is a key which has access to proprietary files (Ki and OP/OPC). + That said, ADM key inherits permissions of ADM2 key and thus we rarely + need ADM2 key per se. + ''' + (res, sw) = self._scc.verify_chv(self._adm2_chv_num, key) + return sw - def verify_adm2(self, key): - ''' - Authenticate with ADM2 key. + def read_ki(self): + """ + Read Ki in proprietary file. - Fairwaves SIM cards support hierarchical key structure and ADM2 key - is a key which has access to proprietary files (Ki and OP/OPC). - That said, ADM key inherits permissions of ADM2 key and thus we rarely - need ADM2 key per se. - ''' - (res, sw) = self._scc.verify_chv(self._adm2_chv_num, key) - return sw + Requires ADM1 access level + """ + return self._scc.read_binary(self._EF['Ki']) + def update_ki(self, ki): + """ + Set Ki in proprietary file. - def read_ki(self): - """ - Read Ki in proprietary file. + Requires ADM1 access level + """ + data, sw = self._scc.update_binary(self._EF['Ki'], ki) + return sw - Requires ADM1 access level - """ - return self._scc.read_binary(self._EF['Ki']) + def read_op_opc(self): + """ + Read Ki in proprietary file. + Requires ADM1 access level + """ + (ef, sw) = self._scc.read_binary(self._EF['OP/OPC']) + type = 'OP' if ef[0:2] == '00' else 'OPC' + return ((type, ef[2:]), sw) - def update_ki(self, ki): - """ - Set Ki in proprietary file. + def update_op(self, op): + """ + Set OP in proprietary file. - Requires ADM1 access level - """ - data, sw = self._scc.update_binary(self._EF['Ki'], ki) - return sw + Requires ADM1 access level + """ + content = '00' + op + data, sw = self._scc.update_binary(self._EF['OP/OPC'], content) + return sw + def update_opc(self, opc): + """ + Set OPC in proprietary file. - def read_op_opc(self): - """ - Read Ki in proprietary file. + Requires ADM1 access level + """ + content = '01' + opc + data, sw = self._scc.update_binary(self._EF['OP/OPC'], content) + return sw - Requires ADM1 access level - """ - (ef, sw) = self._scc.read_binary(self._EF['OP/OPC']) - type = 'OP' if ef[0:2] == '00' else 'OPC' - return ((type, ef[2:]), sw) + def program(self, p): + # For some reason the card programming only works when the card + # is handled as a classic SIM, even though it is an USIM, so we + # reconfigure the class byte and the select control field on + # the fly. When the programming is done the original values are + # restored. + cla_byte_orig = self._scc.cla_byte + sel_ctrl_orig = self._scc.sel_ctrl + self._scc.cla_byte = "a0" + self._scc.sel_ctrl = "0000" + try: + self._program(p) + finally: + # restore original cla byte and sel ctrl + self._scc.cla_byte = cla_byte_orig + self._scc.sel_ctrl = sel_ctrl_orig - def update_op(self, op): - """ - Set OP in proprietary file. + def _program(self, p): + # authenticate as ADM1 + if not p['pin_adm']: + raise ValueError( + "Please provide a PIN-ADM as there is no default one") + self.verify_adm(h2b(p['pin_adm'])) - Requires ADM1 access level - """ - content = '00' + op - data, sw = self._scc.update_binary(self._EF['OP/OPC'], content) - return sw + # TODO: Set operator name + if p.get('smsp') is not None: + sw = self.update_smsp(p['smsp']) + if sw != '9000': + print("Programming SMSP failed with code %s" % sw) + # This SIM doesn't support changing ICCID + if p.get('mcc') is not None and p.get('mnc') is not None: + sw = self.update_hplmn_act(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming MCC/MNC failed with code %s" % sw) + if p.get('imsi') is not None: + sw = self.update_imsi(p['imsi']) + if sw != '9000': + print("Programming IMSI failed with code %s" % sw) + if p.get('ki') is not None: + sw = self.update_ki(p['ki']) + if sw != '9000': + print("Programming Ki failed with code %s" % sw) + if p.get('opc') is not None: + sw = self.update_opc(p['opc']) + if sw != '9000': + print("Programming OPC failed with code %s" % sw) + if p.get('acc') is not None: + sw = self.update_acc(p['acc']) + if sw != '9000': + print("Programming ACC failed with code %s" % sw) - def update_opc(self, opc): - """ - Set OPC in proprietary file. - - Requires ADM1 access level - """ - content = '01' + opc - data, sw = self._scc.update_binary(self._EF['OP/OPC'], content) - return sw - - def program(self, p): - # For some reason the card programming only works when the card - # is handled as a classic SIM, even though it is an USIM, so we - # reconfigure the class byte and the select control field on - # the fly. When the programming is done the original values are - # restored. - cla_byte_orig = self._scc.cla_byte - sel_ctrl_orig = self._scc.sel_ctrl - self._scc.cla_byte = "a0" - self._scc.sel_ctrl = "0000" - - try: - self._program(p) - finally: - # restore original cla byte and sel ctrl - self._scc.cla_byte = cla_byte_orig - self._scc.sel_ctrl = sel_ctrl_orig - - def _program(self, p): - # authenticate as ADM1 - if not p['pin_adm']: - raise ValueError("Please provide a PIN-ADM as there is no default one") - self.verify_adm(h2b(p['pin_adm'])) - - # TODO: Set operator name - if p.get('smsp') is not None: - sw = self.update_smsp(p['smsp']) - if sw != '9000': - print("Programming SMSP failed with code %s"%sw) - # This SIM doesn't support changing ICCID - if p.get('mcc') is not None and p.get('mnc') is not None: - sw = self.update_hplmn_act(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming MCC/MNC failed with code %s"%sw) - if p.get('imsi') is not None: - sw = self.update_imsi(p['imsi']) - if sw != '9000': - print("Programming IMSI failed with code %s"%sw) - if p.get('ki') is not None: - sw = self.update_ki(p['ki']) - if sw != '9000': - print("Programming Ki failed with code %s"%sw) - if p.get('opc') is not None: - sw = self.update_opc(p['opc']) - if sw != '9000': - print("Programming OPC failed with code %s"%sw) - if p.get('acc') is not None: - sw = self.update_acc(p['acc']) - if sw != '9000': - print("Programming ACC failed with code %s"%sw) - class OpenCellsSim(SimCard): - """ - OpenCellsSim + """ + OpenCellsSim - """ + """ - name = 'OpenCells-SIM' + name = 'OpenCells-SIM' - def __init__(self, ssc): - super(OpenCellsSim, self).__init__(ssc) - self._adm_chv_num = 0x0A + def __init__(self, ssc): + super(OpenCellsSim, self).__init__(ssc) + self._adm_chv_num = 0x0A + @classmethod + def autodetect(kls, scc): + try: + # Look for ATR + if scc.get_atr() == toBytes("3B 9F 95 80 1F C3 80 31 E0 73 FE 21 13 57 86 81 02 86 98 44 18 A8"): + return kls(scc) + except: + return None + return None - @classmethod - def autodetect(kls, scc): - try: - # Look for ATR - if scc.get_atr() == toBytes("3B 9F 95 80 1F C3 80 31 E0 73 FE 21 13 57 86 81 02 86 98 44 18 A8"): - return kls(scc) - except: - return None - return None + def program(self, p): + if not p['pin_adm']: + raise ValueError( + "Please provide a PIN-ADM as there is no default one") + self._scc.verify_chv(0x0A, h2b(p['pin_adm'])) + # select MF + r = self._scc.select_path(['3f00']) - def program(self, p): - if not p['pin_adm']: - raise ValueError("Please provide a PIN-ADM as there is no default one") - self._scc.verify_chv(0x0A, h2b(p['pin_adm'])) + # write EF.ICCID + data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) - # select MF - r = self._scc.select_path(['3f00']) + r = self._scc.select_path(['7ff0']) - # write EF.ICCID - data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) + # set Ki in proprietary file + data, sw = self._scc.update_binary('FF02', p['ki']) - r = self._scc.select_path(['7ff0']) + # set OPC in proprietary file + data, sw = self._scc.update_binary('FF01', p['opc']) - # set Ki in proprietary file - data, sw = self._scc.update_binary('FF02', p['ki']) + # select DF_GSM + r = self._scc.select_path(['7f20']) - # set OPC in proprietary file - data, sw = self._scc.update_binary('FF01', p['opc']) + # write EF.IMSI + data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) - # select DF_GSM - r = self._scc.select_path(['7f20']) - - # write EF.IMSI - data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi'])) class WavemobileSim(UsimCard): - """ - WavemobileSim + """ + WavemobileSim - """ + """ - name = 'Wavemobile-SIM' + name = 'Wavemobile-SIM' - def __init__(self, ssc): - super(WavemobileSim, self).__init__(ssc) - self._adm_chv_num = 0x0A - self._scc.cla_byte = "00" - self._scc.sel_ctrl = "0004" #request an FCP + def __init__(self, ssc): + super(WavemobileSim, self).__init__(ssc) + self._adm_chv_num = 0x0A + self._scc.cla_byte = "00" + self._scc.sel_ctrl = "0004" # request an FCP - @classmethod - def autodetect(kls, scc): - try: - # Look for ATR - if scc.get_atr() == toBytes("3B 9F 95 80 1F C7 80 31 E0 73 F6 21 13 67 4D 45 16 00 43 01 00 8F"): - return kls(scc) - except: - return None - return None + @classmethod + def autodetect(kls, scc): + try: + # Look for ATR + if scc.get_atr() == toBytes("3B 9F 95 80 1F C7 80 31 E0 73 F6 21 13 67 4D 45 16 00 43 01 00 8F"): + return kls(scc) + except: + return None + return None - def program(self, p): - if not p['pin_adm']: - raise ValueError("Please provide a PIN-ADM as there is no default one") - self.verify_adm(h2b(p['pin_adm'])) + def program(self, p): + if not p['pin_adm']: + raise ValueError( + "Please provide a PIN-ADM as there is no default one") + self.verify_adm(h2b(p['pin_adm'])) - # EF.ICCID - # TODO: Add programming of the ICCID - if p.get('iccid'): - print("Warning: Programming of the ICCID is not implemented for this type of card.") + # EF.ICCID + # TODO: Add programming of the ICCID + if p.get('iccid'): + print( + "Warning: Programming of the ICCID is not implemented for this type of card.") - # KI (Presumably a propritary file) - # TODO: Add programming of KI - if p.get('ki'): - print("Warning: Programming of the KI is not implemented for this type of card.") + # KI (Presumably a propritary file) + # TODO: Add programming of KI + if p.get('ki'): + print( + "Warning: Programming of the KI is not implemented for this type of card.") - # OPc (Presumably a propritary file) - # TODO: Add programming of OPc - if p.get('opc'): - print("Warning: Programming of the OPc is not implemented for this type of card.") + # OPc (Presumably a propritary file) + # TODO: Add programming of OPc + if p.get('opc'): + print( + "Warning: Programming of the OPc is not implemented for this type of card.") - # EF.SMSP - if p.get('smsp'): - sw = self.update_smsp(p['smsp']) - if sw != '9000': - print("Programming SMSP failed with code %s"%sw) + # EF.SMSP + if p.get('smsp'): + sw = self.update_smsp(p['smsp']) + if sw != '9000': + print("Programming SMSP failed with code %s" % sw) - # EF.IMSI - if p.get('imsi'): - sw = self.update_imsi(p['imsi']) - if sw != '9000': - print("Programming IMSI failed with code %s"%sw) + # EF.IMSI + if p.get('imsi'): + sw = self.update_imsi(p['imsi']) + if sw != '9000': + print("Programming IMSI failed with code %s" % sw) - # EF.ACC - if p.get('acc'): - sw = self.update_acc(p['acc']) - if sw != '9000': - print("Programming ACC failed with code %s"%sw) + # EF.ACC + if p.get('acc'): + sw = self.update_acc(p['acc']) + if sw != '9000': + print("Programming ACC failed with code %s" % sw) - # EF.PLMNsel - if p.get('mcc') and p.get('mnc'): - sw = self.update_plmnsel(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming PLMNsel failed with code %s"%sw) + # EF.PLMNsel + if p.get('mcc') and p.get('mnc'): + sw = self.update_plmnsel(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming PLMNsel failed with code %s" % sw) - # EF.PLMNwAcT - if p.get('mcc') and p.get('mnc'): - sw = self.update_plmn_act(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming PLMNwAcT failed with code %s"%sw) + # EF.PLMNwAcT + if p.get('mcc') and p.get('mnc'): + sw = self.update_plmn_act(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming PLMNwAcT failed with code %s" % sw) - # EF.OPLMNwAcT - if p.get('mcc') and p.get('mnc'): - sw = self.update_oplmn_act(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming OPLMNwAcT failed with code %s"%sw) + # EF.OPLMNwAcT + if p.get('mcc') and p.get('mnc'): + sw = self.update_oplmn_act(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming OPLMNwAcT failed with code %s" % sw) - # EF.AD - if (p.get('mcc') and p.get('mnc')) or p.get('opmode'): - if p.get('mcc') and p.get('mnc'): - mnc = p['mnc'] - else: - mnc = None - sw = self.update_ad(mnc=mnc, opmode=p.get('opmode')) - if sw != '9000': - print("Programming AD failed with code %s"%sw) + # EF.AD + if (p.get('mcc') and p.get('mnc')) or p.get('opmode'): + if p.get('mcc') and p.get('mnc'): + mnc = p['mnc'] + else: + mnc = None + sw = self.update_ad(mnc=mnc, opmode=p.get('opmode')) + if sw != '9000': + print("Programming AD failed with code %s" % sw) - return None + return None class SysmoISIMSJA2(UsimCard, IsimCard): - """ - sysmocom sysmoISIM-SJA2 - """ + """ + sysmocom sysmoISIM-SJA2 + """ - name = 'sysmoISIM-SJA2' + name = 'sysmoISIM-SJA2' - def __init__(self, ssc): - super(SysmoISIMSJA2, self).__init__(ssc) - self._scc.cla_byte = "00" - self._scc.sel_ctrl = "0004" #request an FCP + def __init__(self, ssc): + super(SysmoISIMSJA2, self).__init__(ssc) + self._scc.cla_byte = "00" + self._scc.sel_ctrl = "0004" # request an FCP - @classmethod - def autodetect(kls, scc): - try: - # Try card model #1 - atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9" - if scc.get_atr() == toBytes(atr): - return kls(scc) + @classmethod + def autodetect(kls, scc): + try: + # Try card model #1 + atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9" + if scc.get_atr() == toBytes(atr): + return kls(scc) - # Try card model #2 - atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2" - if scc.get_atr() == toBytes(atr): - return kls(scc) + # Try card model #2 + atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2" + if scc.get_atr() == toBytes(atr): + return kls(scc) - # Try card model #3 - atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5" - if scc.get_atr() == toBytes(atr): - return kls(scc) - except: - return None - return None + # Try card model #3 + atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5" + if scc.get_atr() == toBytes(atr): + return kls(scc) + except: + return None + return None - def verify_adm(self, key): - # authenticate as ADM using default key (written on the card..) - if not key: - raise ValueError("Please provide a PIN-ADM as there is no default one") - (res, sw) = self._scc.verify_chv(0x0A, key) - return sw + def verify_adm(self, key): + # authenticate as ADM using default key (written on the card..) + if not key: + raise ValueError( + "Please provide a PIN-ADM as there is no default one") + (res, sw) = self._scc.verify_chv(0x0A, key) + return sw - def program(self, p): - self.verify_adm(h2b(p['pin_adm'])) + def program(self, p): + self.verify_adm(h2b(p['pin_adm'])) - # This type of card does not allow to reprogram the ICCID. - # Reprogramming the ICCID would mess up the card os software - # license management, so the ICCID must be kept at its factory - # setting! - if p.get('iccid'): - print("Warning: Programming of the ICCID is not implemented for this type of card.") + # This type of card does not allow to reprogram the ICCID. + # Reprogramming the ICCID would mess up the card os software + # license management, so the ICCID must be kept at its factory + # setting! + if p.get('iccid'): + print( + "Warning: Programming of the ICCID is not implemented for this type of card.") - # select DF_GSM - self._scc.select_path(['7f20']) + # select DF_GSM + self._scc.select_path(['7f20']) - # set Service Provider Name - if p.get('name') is not None: - self.update_spn(p['name'], True, True) + # set Service Provider Name + if p.get('name') is not None: + self.update_spn(p['name'], True, True) - # write EF.IMSI - if p.get('imsi'): - self._scc.update_binary('6f07', enc_imsi(p['imsi'])) + # write EF.IMSI + if p.get('imsi'): + self._scc.update_binary('6f07', enc_imsi(p['imsi'])) - # EF.PLMNsel - if p.get('mcc') and p.get('mnc'): - sw = self.update_plmnsel(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming PLMNsel failed with code %s"%sw) + # EF.PLMNsel + if p.get('mcc') and p.get('mnc'): + sw = self.update_plmnsel(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming PLMNsel failed with code %s" % sw) - # EF.PLMNwAcT - if p.get('mcc') and p.get('mnc'): - sw = self.update_plmn_act(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming PLMNwAcT failed with code %s"%sw) + # EF.PLMNwAcT + if p.get('mcc') and p.get('mnc'): + sw = self.update_plmn_act(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming PLMNwAcT failed with code %s" % sw) - # EF.OPLMNwAcT - if p.get('mcc') and p.get('mnc'): - sw = self.update_oplmn_act(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming OPLMNwAcT failed with code %s"%sw) + # EF.OPLMNwAcT + if p.get('mcc') and p.get('mnc'): + sw = self.update_oplmn_act(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming OPLMNwAcT failed with code %s" % sw) - # EF.HPLMNwAcT - if p.get('mcc') and p.get('mnc'): - sw = self.update_hplmn_act(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming HPLMNwAcT failed with code %s"%sw) + # EF.HPLMNwAcT + if p.get('mcc') and p.get('mnc'): + sw = self.update_hplmn_act(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming HPLMNwAcT failed with code %s" % sw) - # EF.AD - if (p.get('mcc') and p.get('mnc')) or p.get('opmode'): - if p.get('mcc') and p.get('mnc'): - mnc = p['mnc'] - else: - mnc = None - sw = self.update_ad(mnc=mnc, opmode=p.get('opmode')) - if sw != '9000': - print("Programming AD failed with code %s"%sw) + # EF.AD + if (p.get('mcc') and p.get('mnc')) or p.get('opmode'): + if p.get('mcc') and p.get('mnc'): + mnc = p['mnc'] + else: + mnc = None + sw = self.update_ad(mnc=mnc, opmode=p.get('opmode')) + if sw != '9000': + print("Programming AD failed with code %s" % sw) - # EF.SMSP - if p.get('smsp'): - r = self._scc.select_path(['3f00', '7f10']) - data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True) + # EF.SMSP + if p.get('smsp'): + r = self._scc.select_path(['3f00', '7f10']) + data, sw = self._scc.update_record( + '6f42', 1, lpad(p['smsp'], 104), force_len=True) - # EF.MSISDN - # TODO: Alpha Identifier (currently 'ff'O * 20) - # TODO: Capability/Configuration1 Record Identifier - # TODO: Extension1 Record Identifier - if p.get('msisdn') is not None: - msisdn = enc_msisdn(p['msisdn']) - content = 'ff' * 20 + msisdn + # EF.MSISDN + # TODO: Alpha Identifier (currently 'ff'O * 20) + # TODO: Capability/Configuration1 Record Identifier + # TODO: Extension1 Record Identifier + if p.get('msisdn') is not None: + msisdn = enc_msisdn(p['msisdn']) + content = 'ff' * 20 + msisdn - r = self._scc.select_path(['3f00', '7f10']) - data, sw = self._scc.update_record('6F40', 1, content, force_len=True) + r = self._scc.select_path(['3f00', '7f10']) + data, sw = self._scc.update_record( + '6F40', 1, content, force_len=True) - # EF.ACC - if p.get('acc'): - sw = self.update_acc(p['acc']) - if sw != '9000': - print("Programming ACC failed with code %s"%sw) + # EF.ACC + if p.get('acc'): + sw = self.update_acc(p['acc']) + if sw != '9000': + print("Programming ACC failed with code %s" % sw) - # Populate AIDs - self.read_aids() + # Populate AIDs + self.read_aids() - # update EF-SIM_AUTH_KEY (and EF-USIM_AUTH_KEY_2G, which is - # hard linked to EF-USIM_AUTH_KEY) - self._scc.select_path(['3f00']) - self._scc.select_path(['a515']) - if p.get('ki'): - self._scc.update_binary('6f20', p['ki'], 1) - if p.get('opc'): - self._scc.update_binary('6f20', p['opc'], 17) + # update EF-SIM_AUTH_KEY (and EF-USIM_AUTH_KEY_2G, which is + # hard linked to EF-USIM_AUTH_KEY) + self._scc.select_path(['3f00']) + self._scc.select_path(['a515']) + if p.get('ki'): + self._scc.update_binary('6f20', p['ki'], 1) + if p.get('opc'): + self._scc.update_binary('6f20', p['opc'], 17) - # update EF-USIM_AUTH_KEY in ADF.ISIM - data, sw = self.select_adf_by_aid(adf="isim") - if sw == '9000': - if p.get('ki'): - self._scc.update_binary('af20', p['ki'], 1) - if p.get('opc'): - self._scc.update_binary('af20', p['opc'], 17) + # update EF-USIM_AUTH_KEY in ADF.ISIM + data, sw = self.select_adf_by_aid(adf="isim") + if sw == '9000': + if p.get('ki'): + self._scc.update_binary('af20', p['ki'], 1) + if p.get('opc'): + self._scc.update_binary('af20', p['opc'], 17) - # update EF.P-CSCF in ADF.ISIM - if self.file_exists(EF_ISIM_ADF_map['PCSCF']): - if p.get('pcscf'): - sw = self.update_pcscf(p['pcscf']) - else: - sw = self.update_pcscf("") - if sw != '9000': - print("Programming P-CSCF failed with code %s"%sw) + # update EF.P-CSCF in ADF.ISIM + if self.file_exists(EF_ISIM_ADF_map['PCSCF']): + if p.get('pcscf'): + sw = self.update_pcscf(p['pcscf']) + else: + sw = self.update_pcscf("") + if sw != '9000': + print("Programming P-CSCF failed with code %s" % sw) + # update EF.DOMAIN in ADF.ISIM + if self.file_exists(EF_ISIM_ADF_map['DOMAIN']): + if p.get('ims_hdomain'): + sw = self.update_domain(domain=p['ims_hdomain']) + else: + sw = self.update_domain() - # update EF.DOMAIN in ADF.ISIM - if self.file_exists(EF_ISIM_ADF_map['DOMAIN']): - if p.get('ims_hdomain'): - sw = self.update_domain(domain=p['ims_hdomain']) - else: - sw = self.update_domain() + if sw != '9000': + print( + "Programming Home Network Domain Name failed with code %s" % sw) - if sw != '9000': - print("Programming Home Network Domain Name failed with code %s"%sw) + # update EF.IMPI in ADF.ISIM + # TODO: Validate IMPI input + if self.file_exists(EF_ISIM_ADF_map['IMPI']): + if p.get('impi'): + sw = self.update_impi(p['impi']) + else: + sw = self.update_impi() + if sw != '9000': + print("Programming IMPI failed with code %s" % sw) - # update EF.IMPI in ADF.ISIM - # TODO: Validate IMPI input - if self.file_exists(EF_ISIM_ADF_map['IMPI']): - if p.get('impi'): - sw = self.update_impi(p['impi']) - else: - sw = self.update_impi() - if sw != '9000': - print("Programming IMPI failed with code %s"%sw) + # update EF.IMPU in ADF.ISIM + # TODO: Validate IMPU input + # Support multiple IMPU if there is enough space + if self.file_exists(EF_ISIM_ADF_map['IMPU']): + if p.get('impu'): + sw = self.update_impu(p['impu']) + else: + sw = self.update_impu() + if sw != '9000': + print("Programming IMPU failed with code %s" % sw) - # update EF.IMPU in ADF.ISIM - # TODO: Validate IMPU input - # Support multiple IMPU if there is enough space - if self.file_exists(EF_ISIM_ADF_map['IMPU']): - if p.get('impu'): - sw = self.update_impu(p['impu']) - else: - sw = self.update_impu() - if sw != '9000': - print("Programming IMPU failed with code %s"%sw) + data, sw = self.select_adf_by_aid(adf="usim") + if sw == '9000': + # update EF-USIM_AUTH_KEY in ADF.USIM + if p.get('ki'): + self._scc.update_binary('af20', p['ki'], 1) + if p.get('opc'): + self._scc.update_binary('af20', p['opc'], 17) - data, sw = self.select_adf_by_aid(adf="usim") - if sw == '9000': - # update EF-USIM_AUTH_KEY in ADF.USIM - if p.get('ki'): - self._scc.update_binary('af20', p['ki'], 1) - if p.get('opc'): - self._scc.update_binary('af20', p['opc'], 17) + # update EF.EHPLMN in ADF.USIM + if self.file_exists(EF_USIM_ADF_map['EHPLMN']): + if p.get('mcc') and p.get('mnc'): + sw = self.update_ehplmn(p['mcc'], p['mnc']) + if sw != '9000': + print("Programming EHPLMN failed with code %s" % sw) - # update EF.EHPLMN in ADF.USIM - if self.file_exists(EF_USIM_ADF_map['EHPLMN']): - if p.get('mcc') and p.get('mnc'): - sw = self.update_ehplmn(p['mcc'], p['mnc']) - if sw != '9000': - print("Programming EHPLMN failed with code %s"%sw) + # update EF.ePDGId in ADF.USIM + if self.file_exists(EF_USIM_ADF_map['ePDGId']): + if p.get('epdgid'): + sw = self.update_epdgid(p['epdgid']) + else: + sw = self.update_epdgid("") + if sw != '9000': + print("Programming ePDGId failed with code %s" % sw) - # update EF.ePDGId in ADF.USIM - if self.file_exists(EF_USIM_ADF_map['ePDGId']): - if p.get('epdgid'): - sw = self.update_epdgid(p['epdgid']) - else: - sw = self.update_epdgid("") - if sw != '9000': - print("Programming ePDGId failed with code %s"%sw) + # update EF.ePDGSelection in ADF.USIM + if self.file_exists(EF_USIM_ADF_map['ePDGSelection']): + if p.get('epdgSelection'): + epdg_plmn = p['epdgSelection'] + sw = self.update_ePDGSelection( + epdg_plmn[:3], epdg_plmn[3:]) + else: + sw = self.update_ePDGSelection("", "") + if sw != '9000': + print("Programming ePDGSelection failed with code %s" % sw) - # update EF.ePDGSelection in ADF.USIM - if self.file_exists(EF_USIM_ADF_map['ePDGSelection']): - if p.get('epdgSelection'): - epdg_plmn = p['epdgSelection'] - sw = self.update_ePDGSelection(epdg_plmn[:3], epdg_plmn[3:]) - else: - sw = self.update_ePDGSelection("", "") - if sw != '9000': - print("Programming ePDGSelection failed with code %s"%sw) + # After successfully programming EF.ePDGId and EF.ePDGSelection, + # Set service 106 and 107 as available in EF.UST + # Disable service 95, 99, 115 if ISIM application is present + if self.file_exists(EF_USIM_ADF_map['UST']): + if p.get('epdgSelection') and p.get('epdgid'): + sw = self.update_ust(106, 1) + if sw != '9000': + print("Programming UST failed with code %s" % sw) + sw = self.update_ust(107, 1) + if sw != '9000': + print("Programming UST failed with code %s" % sw) + sw = self.update_ust(95, 0) + if sw != '9000': + print("Programming UST failed with code %s" % sw) + sw = self.update_ust(99, 0) + if sw != '9000': + print("Programming UST failed with code %s" % sw) + sw = self.update_ust(115, 0) + if sw != '9000': + print("Programming UST failed with code %s" % sw) - # After successfully programming EF.ePDGId and EF.ePDGSelection, - # Set service 106 and 107 as available in EF.UST - # Disable service 95, 99, 115 if ISIM application is present - if self.file_exists(EF_USIM_ADF_map['UST']): - if p.get('epdgSelection') and p.get('epdgid'): - sw = self.update_ust(106, 1) - if sw != '9000': - print("Programming UST failed with code %s"%sw) - sw = self.update_ust(107, 1) - if sw != '9000': - print("Programming UST failed with code %s"%sw) - - sw = self.update_ust(95, 0) - if sw != '9000': - print("Programming UST failed with code %s"%sw) - sw = self.update_ust(99, 0) - if sw != '9000': - print("Programming UST failed with code %s"%sw) - sw = self.update_ust(115, 0) - if sw != '9000': - print("Programming UST failed with code %s"%sw) - - return + return # In order for autodetection ... -_cards_classes = [ FakeMagicSim, SuperSim, MagicSim, GrcardSim, - SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1, - FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2 ] +_cards_classes = [FakeMagicSim, SuperSim, MagicSim, GrcardSim, + SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1, + FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2] + def card_detect(ctype, scc): - # Detect type if needed - card = None - ctypes = dict([(kls.name, kls) for kls in _cards_classes]) + # Detect type if needed + card = None + ctypes = dict([(kls.name, kls) for kls in _cards_classes]) - if ctype == "auto": - for kls in _cards_classes: - card = kls.autodetect(scc) - if card: - print("Autodetected card type: %s" % card.name) - card.reset() - break + if ctype == "auto": + for kls in _cards_classes: + card = kls.autodetect(scc) + if card: + print("Autodetected card type: %s" % card.name) + card.reset() + break - if card is None: - print("Autodetection failed") - return None + if card is None: + print("Autodetection failed") + return None - elif ctype in ctypes: - card = ctypes[ctype](scc) + elif ctype in ctypes: + card = ctypes[ctype](scc) - else: - raise ValueError("Unknown card type: %s" % ctype) + else: + raise ValueError("Unknown card type: %s" % ctype) - return card + return card diff --git a/pySim/cat.py b/pySim/cat.py index b8756e2f..332ad582 100644 --- a/pySim/cat.py +++ b/pySim/cat.py @@ -24,34 +24,48 @@ from construct import * # Tag values as per TS 101 220 Table 7.23 # TS 102 223 Section 8.1 + + class Address(COMPR_TLV_IE, tag=0x06): _construct = Struct('ton_npi'/Int8ub, 'call_number'/BcdAdapter(Bytes(this._.total_len-1))) # TS 102 223 Section 8.2 + + class AlphaIdentifier(COMPR_TLV_IE, tag=0x05): # FIXME: like EF.ADN pass # TS 102 223 Section 8.3 + + class Subaddress(COMPR_TLV_IE, tag=0x08): pass # TS 102 223 Section 8.4 + + class CapabilityConfigParams(COMPR_TLV_IE, tag=0x07): pass # TS 31.111 Section 8.5 + + class CBSPage(COMPR_TLV_IE, tag=0x0C): pass # TS 102 223 Section 8.6 + + class CommandDetails(COMPR_TLV_IE, tag=0x01): _construct = Struct('command_number'/Int8ub, 'type_of_command'/Int8ub, 'command_qualifier'/Int8ub) # TS 102 223 Section 8.7 + + class DeviceIdentities(COMPR_TLV_IE, tag=0x82): DEV_IDS = bidict({ 0x01: 'keypad', @@ -90,8 +104,9 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82): 0x81: 'uicc', 0x82: 'terminal', 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]]} def _to_bytes(self): @@ -100,200 +115,219 @@ class DeviceIdentities(COMPR_TLV_IE, tag=0x82): return bytes([src, dst]) # TS 102 223 Section 8.8 + + class Duration(COMPR_TLV_IE, tag=0x04): _construct = Struct('time_unit'/Int8ub, 'time_interval'/Int8ub) # TS 102 223 Section 8.9 + + class Item(COMPR_TLV_IE, tag=0x0f): _construct = Struct('identifier'/Int8ub, 'text_string'/GsmStringAdapter(GreedyBytes)) # TS 102 223 Section 8.10 + + class ItemIdentifier(COMPR_TLV_IE, tag=0x10): _construct = Struct('identifier'/Int8ub) # TS 102 223 Section 8.11 + + class ResponseLength(COMPR_TLV_IE, tag=0x11): _construct = Struct('minimum_length'/Int8ub, 'maximum_length'/Int8ub) # TS 102 223 Section 8.12 + + class Result(COMPR_TLV_IE, tag=0x03): _construct = Struct('general_result'/Int8ub, 'additional_information'/HexAdapter(GreedyBytes)) - # TS 102 223 Section 8.13 + TS 31.111 Section 8.13 class SMS_TPDU(COMPR_TLV_IE, tag=0x8B): _construct = Struct('tpdu'/HexAdapter(GreedyBytes)) # TS 102 223 Section 8.15 + + class TextString(COMPR_TLV_IE, tag=0x0d): _construct = Struct('dcs'/Int8ub, 'text_string'/HexAdapter(GreedyBytes)) # TS 102 223 Section 8.16 + + class Tone(COMPR_TLV_IE, tag=0x0e): _construct = Struct('tone'/Int8ub) # TS 31 111 Section 8.17 + + class USSDString(COMPR_TLV_IE, tag=0x0a): _construct = Struct('dcs'/Int8ub, 'ussd_string'/HexAdapter(GreedyBytes)) - # TS 101 220 Table 7.17 class ProactiveCommand(BER_TLV_IE, tag=0xD0): pass # TS 101 220 Table 7.17 + 31.111 7.1.1.2 + + class SMSPPDownload(BER_TLV_IE, tag=0xD1, nested=[DeviceIdentities, Address, SMS_TPDU]): pass # TS 101 220 Table 7.17 + 31.111 7.1.1.3 + + class SMSCBDownload(BER_TLV_IE, tag=0xD2, nested=[DeviceIdentities, CBSPage]): pass + class USSDDownload(BER_TLV_IE, tag=0xD9, - nested=[DeviceIdentities, USSDString]): + nested=[DeviceIdentities, USSDString]): pass # reasonable default for playing with OTA # 010203040506070809101112131415161718192021222324252627282930313233 -#'7fe1e10e000000000000001f43000000ff00000000000000000000000000000000' +# '7fe1e10e000000000000001f43000000ff00000000000000000000000000000000' # TS 102 223 Section 5.2 term_prof_bits = { - # first byte - 1: 'Profile download', - 2: 'SMS-PP data download', - 3: 'Cell Broadcast data download', - 4: 'Menu selection', - 5: 'SMS-PP data download', - 6: 'Timer expiration', - 7: 'USSD string DO support in CC by USIM', - 8: 'Call Control by NAA', + # first byte + 1: 'Profile download', + 2: 'SMS-PP data download', + 3: 'Cell Broadcast data download', + 4: 'Menu selection', + 5: 'SMS-PP data download', + 6: 'Timer expiration', + 7: 'USSD string DO support in CC by USIM', + 8: 'Call Control by NAA', - # first byte - 9: 'Command result', - 10: 'Call Control by NAA', - 11: 'Call Control by NAA', - 12: 'MO short message control support', - 13: 'Call Control by NAA', - 14: 'UCS2 Entry supported', - 15: 'UCS2 Display supported', - 16: 'Display Text', + # first byte + 9: 'Command result', + 10: 'Call Control by NAA', + 11: 'Call Control by NAA', + 12: 'MO short message control support', + 13: 'Call Control by NAA', + 14: 'UCS2 Entry supported', + 15: 'UCS2 Display supported', + 16: 'Display Text', - # third byte - 17: 'Proactive UICC: DISPLAY TEXT', - 18: 'Proactive UICC: GET INKEY', - 19: 'Proactive UICC: GET INPUT', - 20: 'Proactive UICC: MORE TIME', - 21: 'Proactive UICC: PLAY TONE', - 22: 'Proactive UICC: POLL INTERVAL', - 23: 'Proactive UICC: POLLING OFF', - 24: 'Proactive UICC: REFRESH', + # third byte + 17: 'Proactive UICC: DISPLAY TEXT', + 18: 'Proactive UICC: GET INKEY', + 19: 'Proactive UICC: GET INPUT', + 20: 'Proactive UICC: MORE TIME', + 21: 'Proactive UICC: PLAY TONE', + 22: 'Proactive UICC: POLL INTERVAL', + 23: 'Proactive UICC: POLLING OFF', + 24: 'Proactive UICC: REFRESH', - # fourth byte - 25: 'Proactive UICC: SELECT ITEM', - 26: 'Proactive UICC: SEND SHORT MESSAGE with 3GPP-SMS-TPDU', - 27: 'Proactive UICC: SEND SS', - 28: 'Proactive UICC: SEND USSD', - 29: 'Proactive UICC: SET UP CALL', - 30: 'Proactive UICC: SET UP MENU', - 31: 'Proactive UICC: PROVIDE LOCAL INFORMATION (MCC, MNC, LAC, Cell ID & IMEI)', - 32: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)', + # fourth byte + 25: 'Proactive UICC: SELECT ITEM', + 26: 'Proactive UICC: SEND SHORT MESSAGE with 3GPP-SMS-TPDU', + 27: 'Proactive UICC: SEND SS', + 28: 'Proactive UICC: SEND USSD', + 29: 'Proactive UICC: SET UP CALL', + 30: 'Proactive UICC: SET UP MENU', + 31: 'Proactive UICC: PROVIDE LOCAL INFORMATION (MCC, MNC, LAC, Cell ID & IMEI)', + 32: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)', - # fifth byte - 33: 'Proactive UICC: SET UP EVENT LIST', - 34: 'Event: MT call', - 35: 'Event: Call connected', - 36: 'Event: Call disconnected', - 37: 'Event: Location status', - 38: 'Event: User activity', - 39: 'Event: Idle screen available', - 40: 'Event: Card reader status', + # fifth byte + 33: 'Proactive UICC: SET UP EVENT LIST', + 34: 'Event: MT call', + 35: 'Event: Call connected', + 36: 'Event: Call disconnected', + 37: 'Event: Location status', + 38: 'Event: User activity', + 39: 'Event: Idle screen available', + 40: 'Event: Card reader status', - # sixth byte - 41: 'Event: Language selection', - 42: 'Event: Browser Termination', - 43: 'Event: Data aailable', - 44: 'Event: Channel status', - 45: 'Event: Access Technology Change', - 46: 'Event: Display parameters changed', - 47: 'Event: Local Connection', - 48: 'Event: Network Search Mode Change', + # sixth byte + 41: 'Event: Language selection', + 42: 'Event: Browser Termination', + 43: 'Event: Data aailable', + 44: 'Event: Channel status', + 45: 'Event: Access Technology Change', + 46: 'Event: Display parameters changed', + 47: 'Event: Local Connection', + 48: 'Event: Network Search Mode Change', - # seventh byte - 49: 'Proactive UICC: POWER ON CARD', - 50: 'Proactive UICC: POWER OFF CARD', - 51: 'Proactive UICC: PERFORM CARD RESET', - 52: 'Proactive UICC: GET READER STATUS (Card reader status)', - 53: 'Proactive UICC: GET READER STATUS (Card reader identifier)', - # RFU: 3 bit (54,55,56) + # seventh byte + 49: 'Proactive UICC: POWER ON CARD', + 50: 'Proactive UICC: POWER OFF CARD', + 51: 'Proactive UICC: PERFORM CARD RESET', + 52: 'Proactive UICC: GET READER STATUS (Card reader status)', + 53: 'Proactive UICC: GET READER STATUS (Card reader identifier)', + # RFU: 3 bit (54,55,56) - # eighth byte - 57: 'Proactive UICC: TIMER MANAGEMENT (start, stop)', - 58: 'Proactive UICC: TIMER MANAGEMENT (get current value)', - 59: 'Proactive UICC: PROVIDE LOCAL INFORMATION (date, time and time zone)', - 60: 'GET INKEY', - 61: 'SET UP IDLE MODE TEXT', - 62: 'RUN AT COMMAND', - 63: 'SETUP CALL', - 64: 'Call Control by NAA', + # eighth byte + 57: 'Proactive UICC: TIMER MANAGEMENT (start, stop)', + 58: 'Proactive UICC: TIMER MANAGEMENT (get current value)', + 59: 'Proactive UICC: PROVIDE LOCAL INFORMATION (date, time and time zone)', + 60: 'GET INKEY', + 61: 'SET UP IDLE MODE TEXT', + 62: 'RUN AT COMMAND', + 63: 'SETUP CALL', + 64: 'Call Control by NAA', - # ninth byte - 65: 'DISPLAY TEXT', - 66: 'SEND DTMF command', - 67: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)', - 68: 'Proactive UICC: PROVIDE LOCAL INFORMATION (language)', - 69: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Timing Advance)', - 70: 'Proactive UICC: LANGUAGE NOTIFICATION', - 71: 'Proactive UICC: LAUNCH BROWSER', - 72: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Access Technology)', + # ninth byte + 65: 'DISPLAY TEXT', + 66: 'SEND DTMF command', + 67: 'Proactive UICC: PROVIDE LOCAL INFORMATION (NMR)', + 68: 'Proactive UICC: PROVIDE LOCAL INFORMATION (language)', + 69: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Timing Advance)', + 70: 'Proactive UICC: LANGUAGE NOTIFICATION', + 71: 'Proactive UICC: LAUNCH BROWSER', + 72: 'Proactive UICC: PROVIDE LOCAL INFORMATION (Access Technology)', - # tenth byte - 73: 'Soft keys support for SELECT ITEM', - 74: 'Soft keys support for SET UP MENU ITEM', - # RFU: 6 bit (75-80) + # tenth byte + 73: 'Soft keys support for SELECT ITEM', + 74: 'Soft keys support for SET UP MENU ITEM', + # RFU: 6 bit (75-80) - # eleventh byte: max number of soft keys as 8bit value (81..88) + # eleventh byte: max number of soft keys as 8bit value (81..88) - # twelfth byte - 89: 'Proactive UICC: OPEN CHANNEL', - 90: 'Proactive UICC: CLOSE CHANNEL', - 91: 'Proactive UICC: RECEIVE DATA', - 92: 'Proactive UICC: SEND DATA', - 93: 'Proactive UICC: GET CHANNEL STATUS', - 94: 'Proactive UICC: SERVICE SEARCH', - 95: 'Proactive UICC: GET SERVICE INFORMATION', - 96: 'Proactive UICC: DECLARE SERVICE', + # twelfth byte + 89: 'Proactive UICC: OPEN CHANNEL', + 90: 'Proactive UICC: CLOSE CHANNEL', + 91: 'Proactive UICC: RECEIVE DATA', + 92: 'Proactive UICC: SEND DATA', + 93: 'Proactive UICC: GET CHANNEL STATUS', + 94: 'Proactive UICC: SERVICE SEARCH', + 95: 'Proactive UICC: GET SERVICE INFORMATION', + 96: 'Proactive UICC: DECLARE SERVICE', - # thirteenth byte - 97: 'BIP supported Bearer: CSD', - 98: 'BIP supported Bearer: GPRS', - 99: 'BIP supported Bearer: Bluetooth', - 100: 'BIP supported Bearer: IrDA', - 101: 'BIP supported Bearer: RS232', - # 3 bits: number of channels supported (102..104) + # thirteenth byte + 97: 'BIP supported Bearer: CSD', + 98: 'BIP supported Bearer: GPRS', + 99: 'BIP supported Bearer: Bluetooth', + 100: 'BIP supported Bearer: IrDA', + 101: 'BIP supported Bearer: RS232', + # 3 bits: number of channels supported (102..104) - # fourtheenth byte (screen height) - # fifteenth byte (screen width) - # sixeenth byte (screen effects) - # seventeenth byte (BIP supported bearers) - 129: 'BIP: TCP, UICC in client mode, remote connection', - 130: 'BIP: UDP, UICC in client mode, remote connection', - 131: 'BIP: TCP, UICC in server mode', - 132: 'BIP: TCP, UICC in client mode, local connection', - 133: 'BIP: UDP, UICC in client mode, local connection', - 134: 'BIP: direct communication channel', - # 2 bits reserved: 135, 136 + # fourtheenth byte (screen height) + # fifteenth byte (screen width) + # sixeenth byte (screen effects) + # seventeenth byte (BIP supported bearers) + 129: 'BIP: TCP, UICC in client mode, remote connection', + 130: 'BIP: UDP, UICC in client mode, remote connection', + 131: 'BIP: TCP, UICC in server mode', + 132: 'BIP: TCP, UICC in client mode, local connection', + 133: 'BIP: UDP, UICC in client mode, local connection', + 134: 'BIP: direct communication channel', + # 2 bits reserved: 135, 136 - # FIXME: remainder + # FIXME: remainder } diff --git a/pySim/commands.py b/pySim/commands.py index 674e1845..20b9c443 100644 --- a/pySim/commands.py +++ b/pySim/commands.py @@ -26,554 +26,574 @@ from pySim.construct import LV from pySim.utils import rpad, b2h, h2b, sw_match, bertlv_encode_len, Hexstr, h2i, str_sanitize from pySim.exceptions import SwMatchError + class SimCardCommands(object): - def __init__(self, transport): - self._tp = transport - self.cla_byte = "a0" - self.sel_ctrl = "0000" - - # Extract a single FCP item from TLV - def __parse_fcp(self, fcp): - # see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF, - # DF or ADF - from pytlv.TLV import TLV - tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b', '8c', '80', 'ab', 'c6', '81', '88']) - - # pytlv is case sensitive! - fcp = fcp.lower() - - if fcp[0:2] != '62': - raise ValueError('Tag of the FCP template does not match, expected 62 but got %s'%fcp[0:2]) - - # 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 - # checking if the length of the remaining TLV string matches - # what we get in the length field. - # See also ETSI TS 102 221, chapter 11.1.1.3.0 Base coding. - exp_tlv_len = int(fcp[2:4], 16) - if len(fcp[4:]) // 2 == exp_tlv_len: - skip = 4 - else: - exp_tlv_len = int(fcp[2:6], 16) - if len(fcp[4:]) // 2 == exp_tlv_len: - skip = 6 - - # Skip FCP tag and length - tlv = fcp[skip:] - return tlvparser.parse(tlv) - - # Tell the length of a record by the card response - # USIMs respond with an FCP template, which is different - # from what SIMs responds. See also: - # USIM: ETSI TS 102 221, chapter 11.1.1.3 Response Data - # SIM: GSM 11.11, chapter 9.2.1 SELECT - def __record_len(self, r) -> int: - if self.sel_ctrl == "0004": - tlv_parsed = self.__parse_fcp(r[-1]) - file_descriptor = tlv_parsed['82'] - # See also ETSI TS 102 221, chapter 11.1.1.4.3 File Descriptor - return int(file_descriptor[4:8], 16) - else: - return int(r[-1][28:30], 16) - - # Tell the length of a binary file. See also comment - # above. - def __len(self, r) -> int: - if self.sel_ctrl == "0004": - tlv_parsed = self.__parse_fcp(r[-1]) - return int(tlv_parsed['80'], 16) - else: - return int(r[-1][4:8], 16) - - def get_atr(self) -> str: - """Return the ATR of the currently inserted card.""" - return self._tp.get_atr() - - def try_select_path(self, dir_list): - """ Try to select a specified path - - Args: - dir_list : list of hex-string FIDs - """ - - rv = [] - if type(dir_list) is not list: - dir_list = [dir_list] - for i in dir_list: - data, sw = self._tp.send_apdu(self.cla_byte + "a4" + self.sel_ctrl + "02" + i) - rv.append((data, sw)) - if sw != '9000': - return rv - return rv - - def select_path(self, dir_list): - """Execute SELECT for an entire list/path of FIDs. - - Args: - dir_list: list of FIDs representing the path to select - - Returns: - list of return values (FCP in hex encoding) for each element of the path - """ - rv = [] - if type(dir_list) is not list: - dir_list = [dir_list] - for i in dir_list: - data, sw = self.select_file(i) - rv.append(data) - return rv - - def select_file(self, fid:str): - """Execute SELECT a given file by FID. - - Args: - fid : file identifier as hex string - """ - - return self._tp.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + fid) - - def select_adf(self, aid:str): - """Execute SELECT a given Applicaiton ADF. - - Args: - aid : application identifier as hex string - """ - - aidlen = ("0" + format(len(aid) // 2, 'x'))[-2:] - return self._tp.send_apdu_checksw(self.cla_byte + "a4" + "0404" + aidlen + aid) - - def read_binary(self, ef, length:int=None, offset:int=0): - """Execute READD BINARY. - - Args: - ef : string or list of strings indicating name or path of transparent EF - length : number of bytes to read - offset : byte offset in file from which to start reading - """ - r = self.select_path(ef) - if len(r[-1]) == 0: - return (None, None) - if length is None: - length = self.__len(r) - offset - if length < 0: - return (None, None) - - total_data = '' - chunk_offset = 0 - while chunk_offset < length: - chunk_len = min(255, length-chunk_offset) - pdu = self.cla_byte + 'b0%04x%02x' % (offset + chunk_offset, chunk_len) - try: - data, sw = self._tp.send_apdu_checksw(pdu) - except Exception as e: - raise ValueError('%s, failed to read (offset %d)' % (str_sanitize(str(e)), offset)) - total_data += data - chunk_offset += chunk_len - return total_data, sw - - def update_binary(self, ef, data:str, offset:int=0, verify:bool=False, conserve:bool=False): - """Execute UPDATE BINARY. - - Args: - ef : string or list of strings indicating name or path of transparent EF - data : hex string of data to be written - offset : byte offset in file from which to start writing - verify : Whether or not to verify data after write - """ - data_length = len(data) // 2 - - # Save write cycles by reading+comparing before write - if conserve: - data_current, sw = self.read_binary(ef, data_length, offset) - if data_current == data: - return None, sw - - self.select_path(ef) - total_data = '' - chunk_offset = 0 - while chunk_offset < data_length: - chunk_len = min(255, data_length - chunk_offset) - # chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2 - pdu = self.cla_byte + 'd6%04x%02x' % (offset + chunk_offset, chunk_len) + data[chunk_offset*2 : (chunk_offset+chunk_len)*2] - try: - chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu) - except Exception as e: - raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' % \ - (str_sanitize(str(e)), chunk_offset, chunk_len)) - total_data += data - chunk_offset += chunk_len - if verify: - self.verify_binary(ef, data, offset) - return total_data, chunk_sw - - def verify_binary(self, ef, data:str, offset:int=0): - """Verify contents of transparent EF. - - Args: - ef : string or list of strings indicating name or path of transparent EF - data : hex string of expected data - offset : byte offset in file from which to start verifying - """ - res = self.read_binary(ef, len(data) // 2, offset) - if res[0].lower() != data.lower(): - raise ValueError('Binary verification failed (expected %s, got %s)' % (data.lower(), res[0].lower())) - - def read_record(self, ef, rec_no:int): - """Execute READ RECORD. - - Args: - ef : string or list of strings indicating name or path of linear fixed EF - rec_no : record number to read - """ - r = self.select_path(ef) - rec_length = self.__record_len(r) - pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length) - return self._tp.send_apdu_checksw(pdu) - - def update_record(self, ef, rec_no:int, data:str, force_len:bool=False, verify:bool=False, - conserve:bool=False): - """Execute UPDATE RECORD. - - Args: - ef : string or list of strings indicating name or path of linear fixed EF - rec_no : record number to read - data : hex string of data to be written - force_len : enforce record length by using the actual data length - verify : verify data by re-reading the record - conserve : read record and compare it with data, skip write on match - """ - res = self.select_path(ef) - - if force_len: - # enforce the record length by the actual length of the given data input - rec_length = len(data) // 2 - else: - # determine the record length from the select response of the file and pad - # the input data with 0xFF if necessary. In cases where the input data - # exceed we throw an exception. - rec_length = self.__record_len(res) - if (len(data) // 2 > rec_length): - raise ValueError('Data length exceeds record length (expected max %d, got %d)' % (rec_length, len(data) // 2)) - elif (len(data) // 2 < rec_length): - data = rpad(data, rec_length * 2) - - # Save write cycles by reading+comparing before write - if conserve: - data_current, sw = self.read_record(ef, rec_no) - data_current = data_current[0:rec_length*2] - if data_current == data: - return None, sw - - pdu = (self.cla_byte + 'dc%02x04%02x' % (rec_no, rec_length)) + data - res = self._tp.send_apdu_checksw(pdu) - if verify: - self.verify_record(ef, rec_no, data) - return res - - def verify_record(self, ef, rec_no:int, data:str): - """Verify record against given data - - Args: - ef : string or list of strings indicating name or path of linear fixed EF - rec_no : record number to read - data : hex string of data to be verified - """ - res = self.read_record(ef, rec_no) - if res[0].lower() != data.lower(): - raise ValueError('Record verification failed (expected %s, got %s)' % (data.lower(), res[0].lower())) - - def record_size(self, ef): - """Determine the record size of given file. - - Args: - ef : string or list of strings indicating name or path of linear fixed EF - """ - r = self.select_path(ef) - return self.__record_len(r) - - def record_count(self, ef): - """Determine the number of records in given file. - - Args: - ef : string or list of strings indicating name or path of linear fixed EF - """ - r = self.select_path(ef) - return self.__len(r) // self.__record_len(r) - - def binary_size(self, ef): - """Determine the size of given transparent file. - - Args: - ef : string or list of strings indicating name or path of transparent EF - """ - r = self.select_path(ef) - return self.__len(r) - - # TS 102 221 Section 11.3.1 low-level helper - def _retrieve_data(self, tag:int, first:bool=True): - if first: - pdu = '80cb008001%02x' % (tag) - else: - pdu = '80cb000000' - return self._tp.send_apdu_checksw(pdu) - - def retrieve_data(self, ef, tag:int): - """Execute RETRIEVE DATA, see also TS 102 221 Section 11.3.1. - - Args - ef : string or list of strings indicating name or path of transparent EF - tag : BER-TLV Tag of value to be retrieved - """ - r = self.select_path(ef) - if len(r[-1]) == 0: - return (None, None) - total_data = '' - # retrieve first block - data, sw = self._retrieve_data(tag, first=True) - total_data += data - while sw == '62f1' or sw == '62f2': - data, sw = self._retrieve_data(tag, first=False) - total_data += data - return total_data, sw - - # TS 102 221 Section 11.3.2 low-level helper - def _set_data(self, data:str, first:bool=True): - if first: - p1 = 0x80 - else: - p1 = 0x00 - if isinstance(data, bytes) or isinstance(data, bytearray): - data = b2h(data) - pdu = '80db00%02x%02x%s' % (p1, len(data)//2, data) - return self._tp.send_apdu_checksw(pdu) - - def set_data(self, ef, tag:int, value:str, verify:bool=False, conserve:bool=False): - """Execute SET DATA. - - Args - ef : string or list of strings indicating name or path of transparent EF - tag : BER-TLV Tag of value to be stored - value : BER-TLV value to be stored - """ - r = self.select_path(ef) - if len(r[-1]) == 0: - return (None, None) - - # in case of deleting the data, we only have 'tag' but no 'value' - if not value: - return self._set_data('%02x' % tag, first=True) - - # FIXME: proper BER-TLV encode - tl = '%02x%s' % (tag, b2h(bertlv_encode_len(len(value)//2))) - tlv = tl + value - tlv_bin = h2b(tlv) - - first = True - total_len = len(tlv_bin) - remaining = tlv_bin - while len(remaining) > 0: - fragment = remaining[:255] - rdata, sw = self._set_data(fragment, first=first) - first = False - remaining = remaining[255:] - return rdata, sw - - def run_gsm(self, rand:str): - """Execute RUN GSM ALGORITHM. - - Args: - rand : 16 byte random data as hex string (RAND) - """ - if len(rand) != 32: - raise ValueError('Invalid rand') - self.select_path(['3f00', '7f20']) - return self._tp.send_apdu(self.cla_byte + '88000010' + rand) - - def authenticate(self, rand:str, autn:str, context='3g'): - """Execute AUTHENTICATE (USIM/ISIM). - - Args: - rand : 16 byte random data as hex string (RAND) - autn : 8 byte Autentication Token (AUTN) - context : 16 byte random data ('3g' or 'gsm') - """ - # 3GPP TS 31.102 Section 7.1.2.1 - AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV)) - AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV) - AuthResp3GSuccess = Struct(Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV)) - AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess) - # build parameters - cmd_data = {'rand': rand, 'autn': autn} - if context == '3g': - p2 = '81' - elif context == 'gsm': - p2 = '80' - (data, sw) = self._tp.send_apdu_constr_checksw(self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G) - if 'auts' in data: - ret = {'synchronisation_failure': data} - else: - ret = {'successful_3g_authentication': data} - return (ret, sw) - - def status(self): - """Execute a STATUS command as per TS 102 221 Section 11.1.2.""" - return self._tp.send_apdu_checksw('80F20000ff') - - def deactivate_file(self): - """Execute DECATIVATE FILE command as per TS 102 221 Section 11.1.14.""" - return self._tp.send_apdu_constr_checksw(self.cla_byte, '04', '00', '00', None, None, None) - - def activate_file(self, fid): - """Execute ACTIVATE FILE command as per TS 102 221 Section 11.1.15. - - Args: - fid : file identifier as hex string - """ - return self._tp.send_apdu_checksw(self.cla_byte + '44000002' + fid) - - def manage_channel(self, mode='open', lchan_nr=0): - """Execute MANAGE CHANNEL command as per TS 102 221 Section 11.1.17. - - Args: - mode : logical channel operation code ('open' or 'close') - lchan_nr : logical channel number (1-19, 0=assigned by UICC) - """ - if mode == 'close': - p1 = 0x80 - else: - p1 = 0x00 - pdu = self.cla_byte + '70%02x%02x00' % (p1, lchan_nr) - return self._tp.send_apdu_checksw(pdu) - - def reset_card(self): - """Physically reset the card""" - return self._tp.reset_card() - - def _chv_process_sw(self, op_name, chv_no, pin_code, sw): - if sw_match(sw, '63cx'): - raise RuntimeError('Failed to %s chv_no 0x%02X with code 0x%s, %i tries left.' % - (op_name, chv_no, b2h(pin_code).upper(), int(sw[3]))) - elif (sw != '9000'): - raise SwMatchError(sw, '9000') - - def verify_chv(self, chv_no:int, code:str): - """Verify a given CHV (Card Holder Verification == PIN) - - Args: - chv_no : chv number (1=CHV1, 2=CHV2, ...) - code : chv code as hex string - """ - fc = rpad(b2h(code), 16) - data, sw = self._tp.send_apdu(self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc) - self._chv_process_sw('verify', chv_no, code, sw) - return (data, sw) - - def unblock_chv(self, chv_no:int, puk_code:str, pin_code:str): - """Unblock a given CHV (Card Holder Verification == PIN) - - Args: - chv_no : chv number (1=CHV1, 2=CHV2, ...) - puk_code : puk code as hex string - pin_code : new chv code as hex string - """ - fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16) - data, sw = self._tp.send_apdu(self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc) - self._chv_process_sw('unblock', chv_no, pin_code, sw) - return (data, sw) - - def change_chv(self, chv_no:int, pin_code:str, new_pin_code:str): - """Change a given CHV (Card Holder Verification == PIN) - - Args: - chv_no : chv number (1=CHV1, 2=CHV2, ...) - pin_code : current 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) - data, sw = self._tp.send_apdu(self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc) - self._chv_process_sw('change', chv_no, pin_code, sw) - return (data, sw) - - def disable_chv(self, chv_no:int, pin_code:str): - """Disable a given CHV (Card Holder Verification == PIN) - - Args: - chv_no : chv number (1=CHV1, 2=CHV2, ...) - pin_code : current chv code as hex string - new_pin_code : new chv code as hex string - """ - fc = rpad(b2h(pin_code), 16) - data, sw = self._tp.send_apdu(self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc) - self._chv_process_sw('disable', chv_no, pin_code, sw) - return (data, sw) - - def enable_chv(self, chv_no:int, pin_code:str): - """Enable a given CHV (Card Holder Verification == PIN) - - Args: - chv_no : chv number (1=CHV1, 2=CHV2, ...) - pin_code : chv code as hex string - """ - fc = rpad(b2h(pin_code), 16) - data, sw = self._tp.send_apdu(self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc) - self._chv_process_sw('enable', chv_no, pin_code, sw) - return (data, sw) - - def envelope(self, payload:str): - """Send one ENVELOPE command to the SIM - - Args: - payload : payload as hex string - """ - return self._tp.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload)) - - def terminal_profile(self, payload:str): - """Send TERMINAL PROFILE to card - - Args: - payload : payload as hex string - """ - data_length = len(payload) // 2 - data, sw = self._tp.send_apdu(('80100000%02x' % data_length) + payload) - return (data, sw) - - # ETSI TS 102 221 11.1.22 - def suspend_uicc(self, min_len_secs:int=60, max_len_secs:int=43200): - """Send SUSPEND UICC to the card. - - Args: - min_len_secs : mimumum suspend time seconds - max_len_secs : maximum suspend time seconds - """ - def encode_duration(secs:int) -> Hexstr: - if secs >= 10*24*60*60: - return '04%02x' % (secs // (10*24*60*60)) - elif secs >= 24*60*60: - return '03%02x' % (secs // (24*60*60)) - elif secs >= 60*60: - return '02%02x' % (secs // (60*60)) - elif secs >= 60: - return '01%02x' % (secs // 60) - else: - return '00%02x' % secs - def decode_duration(enc:Hexstr) -> int: - time_unit = enc[:2] - length = h2i(enc[2:4]) - if time_unit == '04': - return length * 10*24*60*60 - elif time_unit == '03': - return length * 24*60*60 - elif time_unit == '02': - return length * 60*60 - elif time_unit == '01': - return length * 60 - elif time_unit == '00': - return length - else: - raise ValueError('Time unit must be 0x00..0x04') - min_dur_enc = encode_duration(min_len_secs) - max_dur_enc = encode_duration(max_len_secs) - data, sw = self._tp.send_apdu_checksw('8076000004' + min_dur_enc + max_dur_enc) - negotiated_duration_secs = decode_duration(data[:4]) - resume_token = data[4:] - return (negotiated_duration_secs, resume_token, sw) + def __init__(self, transport): + self._tp = transport + self.cla_byte = "a0" + self.sel_ctrl = "0000" + + # Extract a single FCP item from TLV + def __parse_fcp(self, fcp): + # see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF, + # DF or ADF + from pytlv.TLV import TLV + tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b', + '8c', '80', 'ab', 'c6', '81', '88']) + + # pytlv is case sensitive! + fcp = fcp.lower() + + if fcp[0:2] != '62': + raise ValueError( + 'Tag of the FCP template does not match, expected 62 but got %s' % fcp[0:2]) + + # 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 + # checking if the length of the remaining TLV string matches + # what we get in the length field. + # See also ETSI TS 102 221, chapter 11.1.1.3.0 Base coding. + exp_tlv_len = int(fcp[2:4], 16) + if len(fcp[4:]) // 2 == exp_tlv_len: + skip = 4 + else: + exp_tlv_len = int(fcp[2:6], 16) + if len(fcp[4:]) // 2 == exp_tlv_len: + skip = 6 + + # Skip FCP tag and length + tlv = fcp[skip:] + return tlvparser.parse(tlv) + + # Tell the length of a record by the card response + # USIMs respond with an FCP template, which is different + # from what SIMs responds. See also: + # USIM: ETSI TS 102 221, chapter 11.1.1.3 Response Data + # SIM: GSM 11.11, chapter 9.2.1 SELECT + def __record_len(self, r) -> int: + if self.sel_ctrl == "0004": + tlv_parsed = self.__parse_fcp(r[-1]) + file_descriptor = tlv_parsed['82'] + # See also ETSI TS 102 221, chapter 11.1.1.4.3 File Descriptor + return int(file_descriptor[4:8], 16) + else: + return int(r[-1][28:30], 16) + + # Tell the length of a binary file. See also comment + # above. + def __len(self, r) -> int: + if self.sel_ctrl == "0004": + tlv_parsed = self.__parse_fcp(r[-1]) + return int(tlv_parsed['80'], 16) + else: + return int(r[-1][4:8], 16) + + def get_atr(self) -> str: + """Return the ATR of the currently inserted card.""" + return self._tp.get_atr() + + def try_select_path(self, dir_list): + """ Try to select a specified path + + Args: + dir_list : list of hex-string FIDs + """ + + rv = [] + if type(dir_list) is not list: + dir_list = [dir_list] + for i in dir_list: + data, sw = self._tp.send_apdu( + self.cla_byte + "a4" + self.sel_ctrl + "02" + i) + rv.append((data, sw)) + if sw != '9000': + return rv + return rv + + def select_path(self, dir_list): + """Execute SELECT for an entire list/path of FIDs. + + Args: + dir_list: list of FIDs representing the path to select + + Returns: + list of return values (FCP in hex encoding) for each element of the path + """ + rv = [] + if type(dir_list) is not list: + dir_list = [dir_list] + for i in dir_list: + data, sw = self.select_file(i) + rv.append(data) + return rv + + def select_file(self, fid: str): + """Execute SELECT a given file by FID. + + Args: + fid : file identifier as hex string + """ + + return self._tp.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + fid) + + def select_adf(self, aid: str): + """Execute SELECT a given Applicaiton ADF. + + Args: + aid : application identifier as hex string + """ + + aidlen = ("0" + format(len(aid) // 2, 'x'))[-2:] + return self._tp.send_apdu_checksw(self.cla_byte + "a4" + "0404" + aidlen + aid) + + def read_binary(self, ef, length: int = None, offset: int = 0): + """Execute READD BINARY. + + Args: + ef : string or list of strings indicating name or path of transparent EF + length : number of bytes to read + offset : byte offset in file from which to start reading + """ + r = self.select_path(ef) + if len(r[-1]) == 0: + return (None, None) + if length is None: + length = self.__len(r) - offset + if length < 0: + return (None, None) + + total_data = '' + chunk_offset = 0 + while chunk_offset < length: + chunk_len = min(255, length-chunk_offset) + pdu = self.cla_byte + \ + 'b0%04x%02x' % (offset + chunk_offset, chunk_len) + try: + data, sw = self._tp.send_apdu_checksw(pdu) + except Exception as e: + raise ValueError('%s, failed to read (offset %d)' % + (str_sanitize(str(e)), offset)) + total_data += data + chunk_offset += chunk_len + return total_data, sw + + def update_binary(self, ef, data: str, offset: int = 0, verify: bool = False, conserve: bool = False): + """Execute UPDATE BINARY. + + Args: + ef : string or list of strings indicating name or path of transparent EF + data : hex string of data to be written + offset : byte offset in file from which to start writing + verify : Whether or not to verify data after write + """ + data_length = len(data) // 2 + + # Save write cycles by reading+comparing before write + if conserve: + data_current, sw = self.read_binary(ef, data_length, offset) + if data_current == data: + return None, sw + + self.select_path(ef) + total_data = '' + chunk_offset = 0 + while chunk_offset < data_length: + chunk_len = min(255, data_length - chunk_offset) + # chunk_offset is bytes, but data slicing is hex chars, so we need to multiply by 2 + pdu = self.cla_byte + \ + 'd6%04x%02x' % (offset + chunk_offset, chunk_len) + \ + data[chunk_offset*2: (chunk_offset+chunk_len)*2] + try: + chunk_data, chunk_sw = self._tp.send_apdu_checksw(pdu) + except Exception as e: + raise ValueError('%s, failed to write chunk (chunk_offset %d, chunk_len %d)' % + (str_sanitize(str(e)), chunk_offset, chunk_len)) + total_data += data + chunk_offset += chunk_len + if verify: + self.verify_binary(ef, data, offset) + return total_data, chunk_sw + + def verify_binary(self, ef, data: str, offset: int = 0): + """Verify contents of transparent EF. + + Args: + ef : string or list of strings indicating name or path of transparent EF + data : hex string of expected data + offset : byte offset in file from which to start verifying + """ + res = self.read_binary(ef, len(data) // 2, offset) + if res[0].lower() != data.lower(): + raise ValueError('Binary verification failed (expected %s, got %s)' % ( + data.lower(), res[0].lower())) + + def read_record(self, ef, rec_no: int): + """Execute READ RECORD. + + Args: + ef : string or list of strings indicating name or path of linear fixed EF + rec_no : record number to read + """ + r = self.select_path(ef) + rec_length = self.__record_len(r) + pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length) + return self._tp.send_apdu_checksw(pdu) + + def update_record(self, ef, rec_no: int, data: str, force_len: bool = False, verify: bool = False, + conserve: bool = False): + """Execute UPDATE RECORD. + + Args: + ef : string or list of strings indicating name or path of linear fixed EF + rec_no : record number to read + data : hex string of data to be written + force_len : enforce record length by using the actual data length + verify : verify data by re-reading the record + conserve : read record and compare it with data, skip write on match + """ + res = self.select_path(ef) + + if force_len: + # enforce the record length by the actual length of the given data input + rec_length = len(data) // 2 + else: + # determine the record length from the select response of the file and pad + # the input data with 0xFF if necessary. In cases where the input data + # exceed we throw an exception. + rec_length = self.__record_len(res) + if (len(data) // 2 > rec_length): + raise ValueError('Data length exceeds record length (expected max %d, got %d)' % ( + rec_length, len(data) // 2)) + elif (len(data) // 2 < rec_length): + data = rpad(data, rec_length * 2) + + # Save write cycles by reading+comparing before write + if conserve: + data_current, sw = self.read_record(ef, rec_no) + data_current = data_current[0:rec_length*2] + if data_current == data: + return None, sw + + pdu = (self.cla_byte + 'dc%02x04%02x' % (rec_no, rec_length)) + data + res = self._tp.send_apdu_checksw(pdu) + if verify: + self.verify_record(ef, rec_no, data) + return res + + def verify_record(self, ef, rec_no: int, data: str): + """Verify record against given data + + Args: + ef : string or list of strings indicating name or path of linear fixed EF + rec_no : record number to read + data : hex string of data to be verified + """ + res = self.read_record(ef, rec_no) + if res[0].lower() != data.lower(): + raise ValueError('Record verification failed (expected %s, got %s)' % ( + data.lower(), res[0].lower())) + + def record_size(self, ef): + """Determine the record size of given file. + + Args: + ef : string or list of strings indicating name or path of linear fixed EF + """ + r = self.select_path(ef) + return self.__record_len(r) + + def record_count(self, ef): + """Determine the number of records in given file. + + Args: + ef : string or list of strings indicating name or path of linear fixed EF + """ + r = self.select_path(ef) + return self.__len(r) // self.__record_len(r) + + def binary_size(self, ef): + """Determine the size of given transparent file. + + Args: + ef : string or list of strings indicating name or path of transparent EF + """ + r = self.select_path(ef) + return self.__len(r) + + # TS 102 221 Section 11.3.1 low-level helper + def _retrieve_data(self, tag: int, first: bool = True): + if first: + pdu = '80cb008001%02x' % (tag) + else: + pdu = '80cb000000' + return self._tp.send_apdu_checksw(pdu) + + def retrieve_data(self, ef, tag: int): + """Execute RETRIEVE DATA, see also TS 102 221 Section 11.3.1. + + Args + ef : string or list of strings indicating name or path of transparent EF + tag : BER-TLV Tag of value to be retrieved + """ + r = self.select_path(ef) + if len(r[-1]) == 0: + return (None, None) + total_data = '' + # retrieve first block + data, sw = self._retrieve_data(tag, first=True) + total_data += data + while sw == '62f1' or sw == '62f2': + data, sw = self._retrieve_data(tag, first=False) + total_data += data + return total_data, sw + + # TS 102 221 Section 11.3.2 low-level helper + def _set_data(self, data: str, first: bool = True): + if first: + p1 = 0x80 + else: + p1 = 0x00 + if isinstance(data, bytes) or isinstance(data, bytearray): + data = b2h(data) + pdu = '80db00%02x%02x%s' % (p1, len(data)//2, data) + return self._tp.send_apdu_checksw(pdu) + + def set_data(self, ef, tag: int, value: str, verify: bool = False, conserve: bool = False): + """Execute SET DATA. + + Args + ef : string or list of strings indicating name or path of transparent EF + tag : BER-TLV Tag of value to be stored + value : BER-TLV value to be stored + """ + r = self.select_path(ef) + if len(r[-1]) == 0: + return (None, None) + + # in case of deleting the data, we only have 'tag' but no 'value' + if not value: + return self._set_data('%02x' % tag, first=True) + + # FIXME: proper BER-TLV encode + tl = '%02x%s' % (tag, b2h(bertlv_encode_len(len(value)//2))) + tlv = tl + value + tlv_bin = h2b(tlv) + + first = True + total_len = len(tlv_bin) + remaining = tlv_bin + while len(remaining) > 0: + fragment = remaining[:255] + rdata, sw = self._set_data(fragment, first=first) + first = False + remaining = remaining[255:] + return rdata, sw + + def run_gsm(self, rand: str): + """Execute RUN GSM ALGORITHM. + + Args: + rand : 16 byte random data as hex string (RAND) + """ + if len(rand) != 32: + raise ValueError('Invalid rand') + self.select_path(['3f00', '7f20']) + return self._tp.send_apdu(self.cla_byte + '88000010' + rand) + + def authenticate(self, rand: str, autn: str, context='3g'): + """Execute AUTHENTICATE (USIM/ISIM). + + Args: + rand : 16 byte random data as hex string (RAND) + autn : 8 byte Autentication Token (AUTN) + context : 16 byte random data ('3g' or 'gsm') + """ + # 3GPP TS 31.102 Section 7.1.2.1 + AuthCmd3G = Struct('rand'/LV, 'autn'/Optional(LV)) + AuthResp3GSyncFail = Struct(Const(b'\xDC'), 'auts'/LV) + AuthResp3GSuccess = Struct( + Const(b'\xDB'), 'res'/LV, 'ck'/LV, 'ik'/LV, 'kc'/Optional(LV)) + AuthResp3G = Select(AuthResp3GSyncFail, AuthResp3GSuccess) + # build parameters + cmd_data = {'rand': rand, 'autn': autn} + if context == '3g': + p2 = '81' + elif context == 'gsm': + p2 = '80' + (data, sw) = self._tp.send_apdu_constr_checksw( + self.cla_byte, '88', '00', p2, AuthCmd3G, cmd_data, AuthResp3G) + if 'auts' in data: + ret = {'synchronisation_failure': data} + else: + ret = {'successful_3g_authentication': data} + return (ret, sw) + + def status(self): + """Execute a STATUS command as per TS 102 221 Section 11.1.2.""" + return self._tp.send_apdu_checksw('80F20000ff') + + def deactivate_file(self): + """Execute DECATIVATE FILE command as per TS 102 221 Section 11.1.14.""" + return self._tp.send_apdu_constr_checksw(self.cla_byte, '04', '00', '00', None, None, None) + + def activate_file(self, fid): + """Execute ACTIVATE FILE command as per TS 102 221 Section 11.1.15. + + Args: + fid : file identifier as hex string + """ + return self._tp.send_apdu_checksw(self.cla_byte + '44000002' + fid) + + def manage_channel(self, mode='open', lchan_nr=0): + """Execute MANAGE CHANNEL command as per TS 102 221 Section 11.1.17. + + Args: + mode : logical channel operation code ('open' or 'close') + lchan_nr : logical channel number (1-19, 0=assigned by UICC) + """ + if mode == 'close': + p1 = 0x80 + else: + p1 = 0x00 + pdu = self.cla_byte + '70%02x%02x00' % (p1, lchan_nr) + return self._tp.send_apdu_checksw(pdu) + + def reset_card(self): + """Physically reset the card""" + return self._tp.reset_card() + + def _chv_process_sw(self, op_name, chv_no, pin_code, sw): + if sw_match(sw, '63cx'): + raise RuntimeError('Failed to %s chv_no 0x%02X with code 0x%s, %i tries left.' % + (op_name, chv_no, b2h(pin_code).upper(), int(sw[3]))) + elif (sw != '9000'): + raise SwMatchError(sw, '9000') + + def verify_chv(self, chv_no: int, code: str): + """Verify a given CHV (Card Holder Verification == PIN) + + Args: + chv_no : chv number (1=CHV1, 2=CHV2, ...) + code : chv code as hex string + """ + fc = rpad(b2h(code), 16) + data, sw = self._tp.send_apdu( + self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc) + self._chv_process_sw('verify', chv_no, code, sw) + return (data, sw) + + def unblock_chv(self, chv_no: int, puk_code: str, pin_code: str): + """Unblock a given CHV (Card Holder Verification == PIN) + + Args: + chv_no : chv number (1=CHV1, 2=CHV2, ...) + puk_code : puk code as hex string + pin_code : new chv code as hex string + """ + fc = rpad(b2h(puk_code), 16) + rpad(b2h(pin_code), 16) + data, sw = self._tp.send_apdu( + self.cla_byte + '2C00' + ('%02X' % chv_no) + '10' + fc) + self._chv_process_sw('unblock', chv_no, pin_code, sw) + return (data, sw) + + def change_chv(self, chv_no: int, pin_code: str, new_pin_code: str): + """Change a given CHV (Card Holder Verification == PIN) + + Args: + chv_no : chv number (1=CHV1, 2=CHV2, ...) + pin_code : current 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) + data, sw = self._tp.send_apdu( + self.cla_byte + '2400' + ('%02X' % chv_no) + '10' + fc) + self._chv_process_sw('change', chv_no, pin_code, sw) + return (data, sw) + + def disable_chv(self, chv_no: int, pin_code: str): + """Disable a given CHV (Card Holder Verification == PIN) + + Args: + chv_no : chv number (1=CHV1, 2=CHV2, ...) + pin_code : current chv code as hex string + new_pin_code : new chv code as hex string + """ + fc = rpad(b2h(pin_code), 16) + data, sw = self._tp.send_apdu( + self.cla_byte + '2600' + ('%02X' % chv_no) + '08' + fc) + self._chv_process_sw('disable', chv_no, pin_code, sw) + return (data, sw) + + def enable_chv(self, chv_no: int, pin_code: str): + """Enable a given CHV (Card Holder Verification == PIN) + + Args: + chv_no : chv number (1=CHV1, 2=CHV2, ...) + pin_code : chv code as hex string + """ + fc = rpad(b2h(pin_code), 16) + data, sw = self._tp.send_apdu( + self.cla_byte + '2800' + ('%02X' % chv_no) + '08' + fc) + self._chv_process_sw('enable', chv_no, pin_code, sw) + return (data, sw) + + def envelope(self, payload: str): + """Send one ENVELOPE command to the SIM + + Args: + payload : payload as hex string + """ + return self._tp.send_apdu_checksw('80c20000%02x%s' % (len(payload)//2, payload)) + + def terminal_profile(self, payload: str): + """Send TERMINAL PROFILE to card + + Args: + payload : payload as hex string + """ + data_length = len(payload) // 2 + data, sw = self._tp.send_apdu(('80100000%02x' % data_length) + payload) + return (data, sw) + + # ETSI TS 102 221 11.1.22 + def suspend_uicc(self, min_len_secs: int = 60, max_len_secs: int = 43200): + """Send SUSPEND UICC to the card. + + Args: + min_len_secs : mimumum suspend time seconds + max_len_secs : maximum suspend time seconds + """ + def encode_duration(secs: int) -> Hexstr: + if secs >= 10*24*60*60: + return '04%02x' % (secs // (10*24*60*60)) + elif secs >= 24*60*60: + return '03%02x' % (secs // (24*60*60)) + elif secs >= 60*60: + return '02%02x' % (secs // (60*60)) + elif secs >= 60: + return '01%02x' % (secs // 60) + else: + return '00%02x' % secs + + def decode_duration(enc: Hexstr) -> int: + time_unit = enc[:2] + length = h2i(enc[2:4]) + if time_unit == '04': + return length * 10*24*60*60 + elif time_unit == '03': + return length * 24*60*60 + elif time_unit == '02': + return length * 60*60 + elif time_unit == '01': + return length * 60 + elif time_unit == '00': + return length + else: + raise ValueError('Time unit must be 0x00..0x04') + min_dur_enc = encode_duration(min_len_secs) + max_dur_enc = encode_duration(max_len_secs) + data, sw = self._tp.send_apdu_checksw( + '8076000004' + min_dur_enc + max_dur_enc) + negotiated_duration_secs = decode_duration(data[:4]) + resume_token = data[4:] + return (negotiated_duration_secs, resume_token, sw) diff --git a/pySim/construct.py b/pySim/construct.py index 2a3efd3d..e3f6a88e 100644 --- a/pySim/construct.py +++ b/pySim/construct.py @@ -1,3 +1,5 @@ +from construct.lib.containers import Container, ListContainer +from construct.core import EnumIntegerString import typing from construct import * from pySim.utils import b2h, h2b, swap_nibbles @@ -23,18 +25,24 @@ import gsm0338 class HexAdapter(Adapter): """convert a bytes() type to a string of hex nibbles.""" + def _decode(self, obj, context, path): return b2h(obj) + def _encode(self, obj, context, path): return h2b(obj) + class BcdAdapter(Adapter): """convert a bytes() type to a string of BCD nibbles.""" + def _decode(self, obj, context, path): return swap_nibbles(b2h(obj)) + def _encode(self, obj, context, path): return h2b(swap_nibbles(obj)) + class Rpad(Adapter): """ Encoder appends padding bytes (b'\\xff') up to target size. @@ -54,9 +62,11 @@ class Rpad(Adapter): def _encode(self, obj, context, path): if len(obj) > self.sizeof(): - raise SizeofError("Input ({}) exceeds target size ({})".format(len(obj), self.sizeof())) + raise SizeofError("Input ({}) exceeds target size ({})".format( + len(obj), self.sizeof())) return obj + self.pattern * (self.sizeof() - len(obj)) + class GsmStringAdapter(Adapter): """Convert GSM 03.38 encoded bytes to a string.""" @@ -71,6 +81,7 @@ class GsmStringAdapter(Adapter): def _encode(self, obj, context, path): return obj.encode(self.codec, self.err) + def filter_dict(d, exclude_prefix='_'): """filter the input dict to ensure no keys starting with 'exclude_prefix' remain.""" if not isinstance(d, dict): @@ -85,8 +96,6 @@ def filter_dict(d, exclude_prefix='_'): res[key] = value return res -from construct.lib.containers import Container, ListContainer -from construct.core import EnumIntegerString def normalize_construct(c): """Convert a construct specific type to a related base type, mostly useful @@ -95,7 +104,7 @@ def normalize_construct(c): # in the dict: '_io': <_io.BytesIO object at 0x7fdb64e05860> which we cannot json-serialize c = filter_dict(c) 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): r = [normalize_construct(x) for x in c] elif isinstance(c, list): @@ -106,13 +115,15 @@ def normalize_construct(c): r = c 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().""" if not length: length = len(raw_bin_data) parsed = c.parse(raw_bin_data, total_len=length) return normalize_construct(parsed) + # here we collect some shared / common definitions of data types LV = Prefixed(Int8ub, HexAdapter(GreedyBytes)) @@ -129,6 +140,7 @@ ByteRFU = Default(Byte, __RFU_VALUE) # Field that packs all remaining Reserved for Future Use (RFU) bytes GreedyBytesRFU = Default(GreedyBytes, b'') + def BitsRFU(n=1): ''' Field that packs Reserved for Future Use (RFU) bit(s) @@ -143,6 +155,7 @@ def BitsRFU(n=1): ''' return Default(BitsInteger(n), __RFU_VALUE) + def BytesRFU(n=1): ''' Field that packs Reserved for Future Use (RFU) byte(s) @@ -157,6 +170,7 @@ def BytesRFU(n=1): ''' return Default(Bytes(n), __RFU_VALUE) + def GsmString(n): ''' GSM 03.38 encoded byte string of fixed length n. diff --git a/pySim/exceptions.py b/pySim/exceptions.py index f1d1a186..24aff854 100644 --- a/pySim/exceptions.py +++ b/pySim/exceptions.py @@ -21,34 +21,40 @@ # along with this program. If not, see . # + class NoCardError(Exception): - """No card was found in the reader.""" - pass + """No card was found in the reader.""" + pass + class ProtocolError(Exception): - """Some kind of protocol level error interfacing with the card.""" - pass + """Some kind of protocol level error interfacing with the card.""" + pass + class ReaderError(Exception): - """Some kind of general error with the card reader.""" - pass + """Some kind of general error with the card reader.""" + pass + class SwMatchError(Exception): - """Raised when an operation specifies an expected SW but the actual SW from - the card doesn't match.""" - def __init__(self, sw_actual:str, sw_expected:str, rs=None): - """ - Args: - sw_actual : the SW we actually received from the card (4 hex digits) - sw_expected : the SW we expected to receive from the card (4 hex digits) - rs : interpreter class to convert SW to string - """ - self.sw_actual = sw_actual - self.sw_expected = sw_expected - self.rs = rs - def __str__(self): - if self.rs: - r = self.rs.interpret_sw(self.sw_actual) - if r: - return "SW match failed! Expected %s and got %s: %s - %s" % (self.sw_expected, self.sw_actual, r[0], r[1]) - return "SW match failed! Expected %s and got %s." % (self.sw_expected, self.sw_actual) + """Raised when an operation specifies an expected SW but the actual SW from + the card doesn't match.""" + + def __init__(self, sw_actual: str, sw_expected: str, rs=None): + """ + Args: + sw_actual : the SW we actually received from the card (4 hex digits) + sw_expected : the SW we expected to receive from the card (4 hex digits) + rs : interpreter class to convert SW to string + """ + self.sw_actual = sw_actual + self.sw_expected = sw_expected + self.rs = rs + + def __str__(self): + if self.rs: + r = self.rs.interpret_sw(self.sw_actual) + if r: + return "SW match failed! Expected %s and got %s: %s - %s" % (self.sw_expected, self.sw_actual, r[0], r[1]) + return "SW match failed! Expected %s and got %s." % (self.sw_expected, self.sw_actual) diff --git a/pySim/filesystem.py b/pySim/filesystem.py index cfca708c..a323c5b3 100644 --- a/pySim/filesystem.py +++ b/pySim/filesystem.py @@ -44,6 +44,7 @@ from pySim.exceptions import * from pySim.jsonpath import js_path_find, js_path_modify from pySim.commands import SimCardCommands + class CardFile(object): """Base class for all objects in the smart card filesystem. Serve as a common ancestor to all other file types; rarely used directly. @@ -51,8 +52,8 @@ class CardFile(object): RESERVED_NAMES = ['..', '.', '/', 'MF'] RESERVED_FIDS = ['3f00'] - def __init__(self, fid:str=None, sfid:str=None, name:str=None, desc:str=None, - parent:Optional['CardDF']=None, profile:Optional['CardProfile']=None): + def __init__(self, fid: str = None, sfid: str = None, name: str = None, desc: str = None, + parent: Optional['CardDF'] = None, profile: Optional['CardProfile'] = None): """ Args: fid : File Identifier (4 hex digits) @@ -74,11 +75,11 @@ class CardFile(object): if self.parent and self.parent != self and self.fid: self.parent.add_file(self) self.profile = profile - self.shell_commands = [] # type: List[CommandSet] + self.shell_commands = [] # type: List[CommandSet] - # Note: the basic properties (fid, name, ect.) are verified when - # the file is attached to a parent file. See method add_file() in - # class Card DF + # Note: the basic properties (fid, name, ect.) are verified when + # the file is attached to a parent file. See method add_file() in + # class Card DF def __str__(self): if self.name: @@ -86,13 +87,13 @@ class CardFile(object): else: 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: return self.name else: 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. Args: @@ -117,7 +118,7 @@ class CardFile(object): node = node.parent 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. Args: @@ -136,7 +137,7 @@ class CardFile(object): sels.update({self.name: self}) 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. Args: @@ -158,11 +159,11 @@ class CardFile(object): if flags == [] or 'MF' in flags: mf = self.get_mf() if mf: - sels.update(mf._get_self_selectables(flags = flags)) - sels.update(mf.get_app_selectables(flags = flags)) + sels.update(mf._get_self_selectables(flags=flags)) + sels.update(mf.get_app_selectables(flags=flags)) 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. Args: @@ -176,18 +177,18 @@ class CardFile(object): sel_keys.sort() 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. Args: - data_hex: Hex string of the select response - """ + data_hex: Hex string of the select response + """ - # When the current file does not implement a custom select response decoder, - # we just ask the parent file to decode the select response. If this method - # is not overloaded by the current file we will again ask the parent file. - # This way we recursively travel up the file system tree until we hit a file - # that does implement a concrete decoder. + # When the current file does not implement a custom select response decoder, + # we just ask the parent file to decode the select response. If this method + # is not overloaded by the current file we will again ask the parent file. + # This way we recursively travel up the file system tree until we hit a file + # that does implement a concrete decoder. if self.parent: return self.parent.decode_select_response(data_hex) @@ -206,6 +207,7 @@ class CardFile(object): return self.parent.get_profile() return None + class CardDF(CardFile): """DF (Dedicated File) in the smart card filesystem. Those are basically sub-directories.""" @@ -225,7 +227,7 @@ class CardDF(CardFile): def __str__(self): 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. Args: child: The new DF/EF to be added @@ -233,7 +235,7 @@ class CardDF(CardFile): """ if not isinstance(child, CardFile): 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)) if child.name in CardFile.RESERVED_NAMES: 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 ignore_existing: return - raise ValueError("File with given fid %s already exists in %s" % (child.fid, self)) + raise ValueError( + "File with given fid %s already exists in %s" % (child.fid, self)) if self.lookup_file_by_sfid(child.sfid): - raise ValueError("File with given sfid %s already exists in %s" % (child.sfid, self)) + raise ValueError( + "File with given sfid %s already exists in %s" % (child.sfid, self)) if self.lookup_file_by_name(child.name): if ignore_existing: return - raise ValueError("File with given name %s already exists in %s" % (child.name, self)) + raise ValueError( + "File with given name %s already exists in %s" % (child.name, self)) self.children[child.fid] = child child.parent = self - 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 Args: @@ -262,7 +267,7 @@ class CardDF(CardFile): for child in children: 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. Args: @@ -275,12 +280,12 @@ class CardDF(CardFile): # global selectables + our children sels = super().get_selectables(flags) if flags == [] or 'FIDS' in flags: - sels.update({x.fid: x for x in self.children.values() if x.fid}) + sels.update({x.fid: x for x in self.children.values() if x.fid}) if flags == [] or 'FNAMES' in flags: - 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 - 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.""" if name == None: return None @@ -289,7 +294,7 @@ class CardDF(CardFile): return i 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.""" if sfid == None: return None @@ -298,7 +303,7 @@ class CardDF(CardFile): return i 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.""" if fid in self.children: return self.children[fid] @@ -307,6 +312,7 @@ class CardDF(CardFile): class CardMF(CardDF): """MF (Master File) in the smart card filesystem""" + def __init__(self, **kwargs): # can be overridden; use setdefault kwargs.setdefault('fid', '3f00') @@ -320,20 +326,20 @@ class CardMF(CardDF): def __str__(self): 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""" if not isinstance(app, CardADF): raise TypeError("Expected an ADF instance") if app.aid in self.applications: raise ValueError("AID %s already exists" % (app.aid)) self.applications[app.aid] = app - app.parent=self + app.parent = self def get_app_names(self): """Get list of completions (AID names)""" 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. Args: @@ -347,22 +353,23 @@ class CardMF(CardDF): sels.update(self.get_app_selectables(flags)) return sels - def get_app_selectables(self, flags = []) -> dict: + def get_app_selectables(self, flags=[]) -> dict: """Get applications by AID + name""" sels = {} 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: - sels.update({x.name: x for x in self.applications.values() if x.name}) + sels.update( + {x.name: x for x in self.applications.values() if x.name}) return sels - def decode_select_response(self, data_hex:str) -> object: + def decode_select_response(self, data_hex: str) -> object: """Decode the response to a SELECT command. This is the fall-back method which automatically defers to the standard decoding method defined by the card profile. When no profile is set, then no decoding is - performed. Specific derived classes (usually ADF) can overload this method to - install specific decoding. + performed. Specific derived classes (usually ADF) can overload this method to + install specific decoding. """ profile = self.get_profile() @@ -372,9 +379,11 @@ class CardMF(CardDF): else: return data_hex + class CardADF(CardDF): """ADF (Application Dedicated File) in the smart card filesystem""" - def __init__(self, aid:str, **kwargs): + + def __init__(self, aid: str, **kwargs): super().__init__(**kwargs) # reference to CardApplication may be set from CardApplication constructor self.application = None # type: Optional[CardApplication] @@ -386,7 +395,7 @@ class CardADF(CardDF): def __str__(self): 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: return self.name else: @@ -395,6 +404,7 @@ class CardADF(CardDF): class CardEF(CardFile): """EF (Entry File) in the smart card filesystem""" + def __init__(self, *, fid, **kwargs): kwargs['fid'] = fid super().__init__(**kwargs) @@ -402,7 +412,7 @@ class CardEF(CardFile): def __str__(self): 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. Args: @@ -412,9 +422,10 @@ class CardEF(CardFile): dict containing all selectable items. Key is identifier (string), value 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.update({x.name:x for x in self.parent.children.values() if x != self}) + sels.update( + {x.name: x for x in self.parent.children.values() if x != self}) return sels @@ -427,12 +438,16 @@ class TransparentEF(CardEF): @with_default_category('Transparent EF Commands') class ShellCommands(CommandSet): """Shell commands specific for transparent EFs.""" + def __init__(self): super().__init__() read_bin_parser = argparse.ArgumentParser() - read_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read') - read_bin_parser.add_argument('--length', type=int, help='Number of bytes to read') + read_bin_parser.add_argument( + '--offset', type=int, default=0, help='Byte offset for start of read') + read_bin_parser.add_argument( + '--length', type=int, help='Number of bytes to read') + @cmd2.with_argparser(read_bin_parser) def do_read_binary(self, opts): """Read binary data from a transparent EF""" @@ -442,6 +457,7 @@ class TransparentEF(CardEF): read_bin_dec_parser = argparse.ArgumentParser() read_bin_dec_parser.add_argument('--oneline', action='store_true', help='No JSON pretty-printing, dump as a single line') + @cmd2.with_argparser(read_bin_dec_parser) def do_read_binary_decoded(self, opts): """Read + decode data from a transparent EF""" @@ -449,8 +465,11 @@ class TransparentEF(CardEF): self._cmd.poutput_json(data, opts.oneline) upd_bin_parser = argparse.ArgumentParser() - upd_bin_parser.add_argument('--offset', type=int, default=0, help='Byte offset for start of read') - upd_bin_parser.add_argument('data', help='Data bytes (hex format) to write') + upd_bin_parser.add_argument( + '--offset', type=int, default=0, help='Byte offset for start of read') + upd_bin_parser.add_argument( + 'data', help='Data bytes (hex format) to write') + @cmd2.with_argparser(upd_bin_parser) def do_update_binary(self, opts): """Update (Write) data of a transparent EF""" @@ -459,15 +478,18 @@ class TransparentEF(CardEF): self._cmd.poutput(data) upd_bin_dec_parser = argparse.ArgumentParser() - upd_bin_dec_parser.add_argument('data', help='Abstract data (JSON format) to write') + upd_bin_dec_parser.add_argument( + 'data', help='Abstract data (JSON format) to write') upd_bin_dec_parser.add_argument('--json-path', type=str, help='JSON path to modify specific element of file only') + @cmd2.with_argparser(upd_bin_dec_parser) def do_update_binary_decoded(self, opts): """Encode + Update (Write) data of a transparent EF""" if opts.json_path: (data_json, sw) = self._cmd.rs.read_binary_dec() - js_path_modify(data_json, opts.json_path, json.loads(opts.data)) + js_path_modify(data_json, opts.json_path, + json.loads(opts.data)) else: data_json = json.loads(opts.data) (data, sw) = self._cmd.rs.update_binary_dec(data_json) @@ -493,9 +515,8 @@ class TransparentEF(CardEF): if data: self._cmd.poutput_json(data) - - def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None, - size={1,None}): + def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None, + size={1, None}): """ Args: fid : File Identifier (4 hex digits) @@ -511,7 +532,7 @@ class TransparentEF(CardEF): self.size = size 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. 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 {'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. 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 {'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. 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.from_dict(abstract_data) return t.to_tlv() - raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) + raise NotImplementedError( + "%s encoder not yet implemented. Patches welcome." % self) - def encode_hex(self, abstract_data:dict) -> str: + def encode_hex(self, abstract_data: dict) -> str: """Encode abstract representation into raw (hex string) data. 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.from_dict(abstract_data) return b2h(t.to_tlv()) - raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) + raise NotImplementedError( + "%s encoder not yet implemented. Patches welcome." % self) class LinFixedEF(CardEF): @@ -627,12 +650,16 @@ class LinFixedEF(CardEF): @with_default_category('Linear Fixed EF Commands') class ShellCommands(CommandSet): """Shell commands specific for Linear Fixed EFs.""" + def __init__(self): super().__init__() read_rec_parser = argparse.ArgumentParser() - read_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read') - read_rec_parser.add_argument('--count', type=int, default=1, help='Number of records to be read, beginning at record_nr') + read_rec_parser.add_argument( + 'record_nr', type=int, help='Number of record to be read') + read_rec_parser.add_argument( + '--count', type=int, default=1, help='Number of records to be read, beginning at record_nr') + @cmd2.with_argparser(read_rec_parser) def do_read_record(self, opts): """Read one or multiple records from a record-oriented EF""" @@ -640,15 +667,17 @@ class LinFixedEF(CardEF): recnr = opts.record_nr + r (data, sw) = self._cmd.rs.read_record(recnr) if (len(data) > 0): - recstr = str(data) + recstr = str(data) else: - recstr = "(empty)" + recstr = "(empty)" self._cmd.poutput("%03d %s" % (recnr, recstr)) read_rec_dec_parser = argparse.ArgumentParser() - read_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read') + read_rec_dec_parser.add_argument( + 'record_nr', type=int, help='Number of record to be read') read_rec_dec_parser.add_argument('--oneline', action='store_true', help='No JSON pretty-printing, dump as a single line') + @cmd2.with_argparser(read_rec_dec_parser) def do_read_record_decoded(self, opts): """Read + decode a record from a record-oriented EF""" @@ -656,6 +685,7 @@ class LinFixedEF(CardEF): self._cmd.poutput_json(data, opts.oneline) read_recs_parser = argparse.ArgumentParser() + @cmd2.with_argparser(read_recs_parser) def do_read_records(self, opts): """Read all records from a record-oriented EF""" @@ -663,14 +693,15 @@ class LinFixedEF(CardEF): for recnr in range(1, 1 + num_of_rec): (data, sw) = self._cmd.rs.read_record(recnr) if (len(data) > 0): - recstr = str(data) + recstr = str(data) else: - recstr = "(empty)" + recstr = "(empty)" self._cmd.poutput("%03d %s" % (recnr, recstr)) read_recs_dec_parser = argparse.ArgumentParser() read_recs_dec_parser.add_argument('--oneline', action='store_true', - help='No JSON pretty-printing, dump as a single line') + help='No JSON pretty-printing, dump as a single line') + @cmd2.with_argparser(read_recs_dec_parser) def do_read_records_decoded(self, opts): """Read + decode all records from a record-oriented EF""" @@ -683,8 +714,11 @@ class LinFixedEF(CardEF): self._cmd.poutput_json(data_list, opts.oneline) upd_rec_parser = argparse.ArgumentParser() - upd_rec_parser.add_argument('record_nr', type=int, help='Number of record to be read') - upd_rec_parser.add_argument('data', help='Data bytes (hex format) to write') + upd_rec_parser.add_argument( + 'record_nr', type=int, help='Number of record to be read') + upd_rec_parser.add_argument( + 'data', help='Data bytes (hex format) to write') + @cmd2.with_argparser(upd_rec_parser) def do_update_record(self, opts): """Update (write) data to a record-oriented EF""" @@ -693,24 +727,31 @@ class LinFixedEF(CardEF): self._cmd.poutput(data) upd_rec_dec_parser = argparse.ArgumentParser() - upd_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be read') - upd_rec_dec_parser.add_argument('data', help='Abstract data (JSON format) to write') + upd_rec_dec_parser.add_argument( + 'record_nr', type=int, help='Number of record to be read') + upd_rec_dec_parser.add_argument( + 'data', help='Abstract data (JSON format) to write') upd_rec_dec_parser.add_argument('--json-path', type=str, help='JSON path to modify specific element of record only') + @cmd2.with_argparser(upd_rec_dec_parser) def do_update_record_decoded(self, opts): """Encode + Update (write) data to a record-oriented EF""" if opts.json_path: (data_json, sw) = self._cmd.rs.read_record_dec(opts.record_nr) - js_path_modify(data_json, opts.json_path, json.loads(opts.data)) + js_path_modify(data_json, opts.json_path, + json.loads(opts.data)) else: data_json = json.loads(opts.data) - (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, data_json) + (data, sw) = self._cmd.rs.update_record_dec( + opts.record_nr, data_json) if data: self._cmd.poutput(data) edit_rec_dec_parser = argparse.ArgumentParser() - edit_rec_dec_parser.add_argument('record_nr', type=int, help='Number of record to be edited') + edit_rec_dec_parser.add_argument( + 'record_nr', type=int, help='Number of record to be edited') + @cmd2.with_argparser(edit_rec_dec_parser) def do_edit_record_decoded(self, opts): """Edit the JSON representation of one record in an editor.""" @@ -727,13 +768,13 @@ class LinFixedEF(CardEF): if edited_json == orig_json: self._cmd.poutput("Data not modified, skipping write") else: - (data, sw) = self._cmd.rs.update_record_dec(opts.record_nr, edited_json) + (data, sw) = self._cmd.rs.update_record_dec( + opts.record_nr, edited_json) if data: self._cmd.poutput_json(data) - - def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, - parent:Optional[CardDF]=None, rec_len={1,None}): + def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, + parent: Optional[CardDF] = None, rec_len={1, None}): """ Args: fid : File Identifier (4 hex digits) @@ -749,7 +790,7 @@ class LinFixedEF(CardEF): self._construct = 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. 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 {'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. 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 {'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. 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.from_dict(abstract_data) return b2h(t.to_tlv()) - raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) + raise NotImplementedError( + "%s encoder not yet implemented. Patches welcome." % self) - def encode_record_bin(self, abstract_data:dict) -> bytearray: + def encode_record_bin(self, abstract_data: dict) -> bytearray: """Encode abstract representation into raw (binary) data. 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.from_dict(abstract_data) return t.to_tlv() - raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) + raise NotImplementedError( + "%s encoder not yet implemented. Patches welcome." % self) + class CyclicEF(LinFixedEF): """Cyclic EF (Entry File) in the smart card filesystem""" # we don't really have any special support for those; just recycling LinFixedEF here - def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None, - rec_len={1,None}): - super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, parent=parent, rec_len=rec_len) + + def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None, + rec_len={1, None}): + super().__init__(fid=fid, sfid=sfid, name=name, + desc=desc, parent=parent, rec_len=rec_len) + class TransRecEF(TransparentEF): """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 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: 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) 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. 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 {'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. 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 {'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. 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.from_dict(abstract_data) return b2h(t.to_tlv()) - raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) + raise NotImplementedError( + "%s encoder not yet implemented. Patches welcome." % self) - def encode_record_bin(self, abstract_data:dict) -> bytearray: + def encode_record_bin(self, abstract_data: dict) -> bytearray: """Encode abstract representation into raw (binary) data. 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.from_dict(abstract_data) return t.to_tlv() - raise NotImplementedError("%s encoder not yet implemented. Patches welcome." % self) + raise NotImplementedError( + "%s encoder not yet implemented. Patches welcome." % self) - def _decode_bin(self, raw_bin_data:bytearray): - chunks = [raw_bin_data[i:i+self.rec_len] for i in range(0, len(raw_bin_data), self.rec_len)] + 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)] return [self.decode_record_bin(x) for x in chunks] def _encode_bin(self, abstract_data) -> bytes: @@ -1014,11 +1065,14 @@ class BerTlvEF(CardEF): @with_default_category('BER-TLV EF Commands') class ShellCommands(CommandSet): """Shell commands specific for BER-TLV EFs.""" + def __init__(self): super().__init__() retrieve_data_parser = argparse.ArgumentParser() - retrieve_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to retrieve') + retrieve_data_parser.add_argument( + 'tag', type=auto_int, help='BER-TLV Tag of value to retrieve') + @cmd2.with_argparser(retrieve_data_parser) def do_retrieve_data(self, opts): """Retrieve (Read) data from a BER-TLV EF""" @@ -1031,8 +1085,11 @@ class BerTlvEF(CardEF): self._cmd.poutput(tags) set_data_parser = argparse.ArgumentParser() - set_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set') - set_data_parser.add_argument('data', help='Data bytes (hex format) to write') + set_data_parser.add_argument( + 'tag', type=auto_int, help='BER-TLV Tag of value to set') + set_data_parser.add_argument( + 'data', help='Data bytes (hex format) to write') + @cmd2.with_argparser(set_data_parser) def do_set_data(self, opts): """Set (Write) data for a given tag in a BER-TLV EF""" @@ -1041,7 +1098,9 @@ class BerTlvEF(CardEF): self._cmd.poutput(data) del_data_parser = argparse.ArgumentParser() - del_data_parser.add_argument('tag', type=auto_int, help='BER-TLV Tag of value to set') + del_data_parser.add_argument( + 'tag', type=auto_int, help='BER-TLV Tag of value to set') + @cmd2.with_argparser(del_data_parser) def do_delete_data(self, opts): """Delete data for a given tag in a BER-TLV EF""" @@ -1049,9 +1108,8 @@ class BerTlvEF(CardEF): if data: self._cmd.poutput(data) - - def __init__(self, fid:str, sfid:str=None, name:str=None, desc:str=None, parent:CardDF=None, - size={1,None}): + def __init__(self, fid: str, sfid: str = None, name: str = None, desc: str = None, parent: CardDF = None, + size={1, None}): """ Args: fid : File Identifier (4 hex digits) @@ -1069,7 +1127,8 @@ class BerTlvEF(CardEF): class RuntimeState(object): """Represent the runtime state of a session with a card.""" - def __init__(self, card, profile:'CardProfile'): + + def __init__(self, card, profile: 'CardProfile'): """ Args: card : pysim.cards.Card instance @@ -1077,12 +1136,13 @@ class RuntimeState(object): """ self.mf = CardMF(profile=profile) self.card = card - self.selected_file = self.mf # type: CardDF + self.selected_file = self.mf # type: CardDF self.profile = profile # make sure the class and selection control bytes, which are specified # by the card profile are used - self.card.set_apdu_parameter(cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl) + self.card.set_apdu_parameter( + cla=self.profile.cla, sel_ctrl=self.profile.sel_ctrl) # add application ADFs + MF-files from profile apps = self._match_applications() @@ -1170,7 +1230,7 @@ class RuntimeState(object): node = node.parent 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 or the underlying card profile. @@ -1191,11 +1251,12 @@ class RuntimeState(object): res = app.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 - object if the file actually exists.""" + object if the file actually exists.""" if not is_hex(fid, 4, 4): - raise ValueError("Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid) + raise ValueError( + "Cannot select unknown file by name %s, only hexadecimal 4 digit FID is allowed" % fid) try: (data, sw) = self.card._scc.select_file(fid) @@ -1207,18 +1268,21 @@ class RuntimeState(object): select_resp = self.selected_file.decode_select_response(data) if (select_resp['file_descriptor']['file_type'] == 'df'): - f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), desc="dedicated file, manually added at runtime") + f = CardDF(fid=fid, sfid=None, name="DF." + str(fid).upper(), + desc="dedicated file, manually added at runtime") else: if (select_resp['file_descriptor']['structure'] == 'transparent'): - f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime") + f = TransparentEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), + desc="elementary file, manually added at runtime") else: - f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), desc="elementary file, manually added at runtime") + f = LinFixedEF(fid=fid, sfid=None, name="EF." + str(fid).upper(), + desc="elementary file, manually added at runtime") self.selected_file.add_files([f]) self.selected_file = f 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, ...). Args: @@ -1265,14 +1329,14 @@ class RuntimeState(object): (data, sw) = self.card._scc.status() 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.""" sels = self.selected_file.get_selectables() f = sels[name] data, sw = self.card._scc.activate_file(f.fid) 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. Args: @@ -1298,7 +1362,7 @@ class RuntimeState(object): dec_data = self.selected_file.decode_hex(data) 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. Args: @@ -1309,7 +1373,7 @@ class RuntimeState(object): raise TypeError("Only works with TransparentEF") 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 then updates the EF with it. @@ -1319,7 +1383,7 @@ class RuntimeState(object): data_hex = self.selected_file.encode_hex(data) 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. Args: @@ -1332,7 +1396,7 @@ class RuntimeState(object): # returns a string of hex nibbles 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. Args: @@ -1343,7 +1407,7 @@ class RuntimeState(object): (data, sw) = self.read_record(rec_nr) 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 Args: @@ -1354,7 +1418,7 @@ class RuntimeState(object): 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) - 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 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) 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. Args: @@ -1390,7 +1454,7 @@ class RuntimeState(object): tag, length, value, remainder = bertlv_parse_one(h2b(data)) 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 Args: @@ -1408,15 +1472,15 @@ class RuntimeState(object): cmd_app.unregister_command_set(c) - class FileData(object): """Represent the runtime, on-card data.""" + def __init__(self, fdesc): self.desc = fdesc self.fcp = None -def interpret_sw(sw_data:dict, sw:str): +def interpret_sw(sw_data: dict, sw: str): """Interpret a given status word. Args: @@ -1435,10 +1499,12 @@ def interpret_sw(sw_data:dict, sw:str): return (class_str, descr) return None + class CardApplication(object): """A card application is represented by an ADF (with contained hierarchy) and optionally some SW definitions.""" - def __init__(self, name, adf:Optional[CardADF]=None, aid:str=None, sw:dict=None): + + def __init__(self, name, adf: Optional[CardADF] = None, aid: str = None, sw: dict = None): """ Args: adf : ADF name @@ -1477,11 +1543,11 @@ class CardModel(abc.ABC): @classmethod @abc.abstractmethod - def add_files(cls, rs:RuntimeState): + def add_files(cls, rs: RuntimeState): """Add model specific files to given RuntimeState.""" @classmethod - def match(cls, scc:SimCardCommands) -> bool: + def match(cls, scc: SimCardCommands) -> bool: """Test if given card matches this model.""" card_atr = scc.get_atr() for atr in cls._atrs: @@ -1492,7 +1558,7 @@ class CardModel(abc.ABC): return False @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 (by ATR or overriding the 'match' method). If so, call their 'add_files' method.""" diff --git a/pySim/gsm_r.py b/pySim/gsm_r.py index 22b88fe6..3364b3dc 100644 --- a/pySim/gsm_r.py +++ b/pySim/gsm_r.py @@ -43,26 +43,32 @@ import pySim.ts_51_011 # DF.EIRENE (FFFIS for GSM-R SIM Cards) ###################################################################### + class FuncNTypeAdapter(Adapter): def _decode(self, obj, context, path): bcd = swap_nibbles(b2h(obj)) last_digit = bcd[-1] return {'functional_number': bcd[:-1], 'presentation_of_only_this_fn': last_digit & 4, - 'permanent_fn': last_digit & 8 } + 'permanent_fn': last_digit & 8} + def _encode(self, obj, context, path): return 'FIXME' + class EF_FN(LinFixedEF): """Section 7.2""" + def __init__(self): - super().__init__(fid='6ff1', sfid=None, name='EF.EN', desc='Functional numbers', rec_len={9,9}) + super().__init__(fid='6ff1', sfid=None, name='EF.EN', + desc='Functional numbers', rec_len={9, 9}) self._construct = Struct('functional_number_and_type'/FuncNTypeAdapter(Bytes(8)), 'list_number'/Int8ub) class PlConfAdapter(Adapter): """Section 7.4.3""" + def _decode(self, obj, context, path): num = int(obj) & 0x7 if num == 0: @@ -77,6 +83,7 @@ class PlConfAdapter(Adapter): return 1 elif num == 5: return 0 + def _encode(self, obj, context, path): if obj == 'None': return 0 @@ -92,8 +99,10 @@ class PlConfAdapter(Adapter): elif obj == 0: return 5 + class PlCallAdapter(Adapter): """Section 7.4.12""" + def _decode(self, obj, context, path): num = int(obj) & 0x7 if num == 0: @@ -112,6 +121,7 @@ class PlCallAdapter(Adapter): return 'B' elif num == 7: return 'A' + def _encode(self, obj, context, path): if obj == 'None': return 0 @@ -130,12 +140,16 @@ class PlCallAdapter(Adapter): elif obj == 'A': return 7 -NextTableType = Enum(Byte, decision=0xf0, predefined=0xf1, num_dial_digits=0xf2, ic=0xf3, empty=0xff) + +NextTableType = Enum(Byte, decision=0xf0, predefined=0xf1, + num_dial_digits=0xf2, ic=0xf3, empty=0xff) + class EF_CallconfC(TransparentEF): """Section 7.3""" + def __init__(self): - super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size={24,24}, + super().__init__(fid='6ff2', sfid=None, name='EF.CallconfC', size={24, 24}, desc='Call Configuration of emergency calls Configuration') self._construct = Struct('pl_conf'/PlConfAdapter(Int8ub), 'conf_nr'/BcdAdapter(Bytes(8)), @@ -147,29 +161,39 @@ class EF_CallconfC(TransparentEF): 'shunting_emergency_gid'/Int8ub, 'imei'/BcdAdapter(Bytes(8))) + class EF_CallconfI(LinFixedEF): """Section 7.5""" + def __init__(self): - super().__init__(fid='6ff3', sfid=None, name='EF.CallconfI', rec_len={21,21}, + super().__init__(fid='6ff3', sfid=None, name='EF.CallconfI', rec_len={21, 21}, desc='Call Configuration of emergency calls Information') self._construct = Struct('t_dur'/Int24ub, 't_relcalc'/Int32ub, 'pl_call'/PlCallAdapter(Int8ub), - 'cause'/FlagsEnum(Int8ub, powered_off=1, radio_link_error=2, user_command=5), + 'cause' / + FlagsEnum(Int8ub, powered_off=1, + radio_link_error=2, user_command=5), 'gcr'/BcdAdapter(Bytes(4)), 'fnr'/BcdAdapter(Bytes(8))) + class EF_Shunting(TransparentEF): """Section 7.6""" + def __init__(self): - super().__init__(fid='6ff4', sfid=None, name='EF.Shunting', desc='Shunting', size={8,8}) + super().__init__(fid='6ff4', sfid=None, + name='EF.Shunting', desc='Shunting', size={8, 8}) self._construct = Struct('common_gid'/Int8ub, 'shunting_gid'/Bytes(7)) + class EF_GsmrPLMN(LinFixedEF): """Section 7.7""" + def __init__(self): - super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN', desc='GSM-R network selection', rec_len={9,9}) + super().__init__(fid='6ff5', sfid=None, name='EF.GsmrPLMN', + desc='GSM-R network selection', rec_len={9, 9}) self._construct = Struct('plmn'/BcdAdapter(Bytes(3)), 'class_of_network'/BitStruct('supported'/FlagsEnum(BitsInteger(5), vbs=1, vgcs=2, emlpp=4, fn=8, eirene=16), 'preference'/BitsInteger(3)), @@ -177,34 +201,46 @@ class EF_GsmrPLMN(LinFixedEF): 'outgoing_ref_tbl'/HexAdapter(Bytes(2)), 'ic_table_ref'/HexAdapter(Bytes(1))) + class EF_IC(LinFixedEF): """Section 7.8""" + def __init__(self): - super().__init__(fid='6f8d', sfid=None, name='EF.IC', desc='International Code', rec_len={7,7}) + super().__init__(fid='6f8d', sfid=None, name='EF.IC', + desc='International Code', rec_len={7, 7}) self._construct = Struct('next_table_type'/NextTableType, 'id_of_next_table'/HexAdapter(Bytes(2)), 'ic_decision_value'/BcdAdapter(Bytes(2)), 'network_string_table_index'/Int8ub) + class EF_NW(LinFixedEF): """Section 7.9""" + def __init__(self): - super().__init__(fid='6f80', sfid=None, name='EF.NW', desc='Network Name', rec_len={8,8}) + super().__init__(fid='6f80', sfid=None, name='EF.NW', + desc='Network Name', rec_len={8, 8}) self._construct = GsmString(8) + class EF_Switching(LinFixedEF): """Section 8.4""" + def __init__(self, fid, name, desc): - super().__init__(fid=fid, sfid=None, name=name, desc=desc, rec_len={6,6}) + super().__init__(fid=fid, sfid=None, + name=name, desc=desc, rec_len={6, 6}) self._construct = Struct('next_table_type'/NextTableType, 'id_of_next_table'/HexAdapter(Bytes(2)), 'decision_value'/BcdAdapter(Bytes(2)), 'string_table_index'/Int8ub) + class EF_Predefined(LinFixedEF): """Section 8.5""" + def __init__(self, fid, name, desc): - super().__init__(fid=fid, sfid=None, name=name, desc=desc, rec_len={3,3}) + super().__init__(fid=fid, sfid=None, + name=name, desc=desc, rec_len={3, 3}) # header and other records have different structure. WTF !?! self._construct = Struct('next_table_type'/NextTableType, 'id_of_next_table'/HexAdapter(Bytes(2)), @@ -212,10 +248,12 @@ class EF_Predefined(LinFixedEF): 'string_table_index1'/Int8ub) # TODO: predefined value n, ... + class EF_DialledVals(TransparentEF): """Section 8.6""" + def __init__(self, fid, name, desc): - super().__init__(fid=fid, sfid=None, name=name, desc=desc, size={4,4}) + super().__init__(fid=fid, sfid=None, name=name, desc=desc, size={4, 4}) self._construct = Struct('next_table_type'/NextTableType, 'id_of_next_table'/HexAdapter(Bytes(2)), '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='6f8f', name='EF.SC', desc='Short Code'), EF_Predefined(fid='6f88', name='EF.FC', desc='Function Code'), - EF_Predefined(fid='6f89', name='EF.Service', desc='VGCS/VBS Service Code'), - EF_Predefined(fid='6f8a', name='EF.Call', desc='First digit of the group ID'), - EF_Predefined(fid='6f8b', name='EF.FctTeam', desc='Call Type 6 Team Type + Team member function'), - EF_Predefined(fid='6f92', name='EF.Controller', desc='Call Type 7 Controller function code'), - EF_Predefined(fid='6f8c', name='EF.Gateway', desc='Access to external networks'), - EF_DialledVals(fid='6f81', name='EF.5to8digits', desc='Call Type 2 User Identity Number length'), - EF_DialledVals(fid='6f82', name='EF.2digits', desc='2 digits input'), - EF_DialledVals(fid='6f83', name='EF.8digits', desc='8 digits input'), - EF_DialledVals(fid='6f84', name='EF.9digits', desc='9 digits input'), - EF_DialledVals(fid='6f85', name='EF.SSSSS', desc='Group call area input'), - EF_DialledVals(fid='6f86', name='EF.LLLLL', desc='Location number Call Type 6'), - EF_DialledVals(fid='6f91', name='EF.Location', desc='Location number Call Type 7'), - EF_DialledVals(fid='6f87', name='EF.FreeNumber', desc='Free Number Call Type 0 and 8'), - ] + EF_Predefined(fid='6f89', name='EF.Service', + desc='VGCS/VBS Service Code'), + EF_Predefined(fid='6f8a', name='EF.Call', + desc='First digit of the group ID'), + EF_Predefined(fid='6f8b', name='EF.FctTeam', + desc='Call Type 6 Team Type + Team member function'), + EF_Predefined(fid='6f92', name='EF.Controller', + desc='Call Type 7 Controller function code'), + EF_Predefined(fid='6f8c', name='EF.Gateway', + desc='Access to external networks'), + EF_DialledVals(fid='6f81', name='EF.5to8digits', + desc='Call Type 2 User Identity Number length'), + EF_DialledVals(fid='6f82', name='EF.2digits', + desc='2 digits input'), + EF_DialledVals(fid='6f83', name='EF.8digits', + desc='8 digits input'), + EF_DialledVals(fid='6f84', name='EF.9digits', + desc='9 digits input'), + EF_DialledVals(fid='6f85', name='EF.SSSSS', + desc='Group call area input'), + EF_DialledVals(fid='6f86', name='EF.LLLLL', + desc='Location number Call Type 6'), + EF_DialledVals(fid='6f91', name='EF.Location', + desc='Location number Call Type 7'), + EF_DialledVals(fid='6f87', name='EF.FreeNumber', + desc='Free Number Call Type 0 and 8'), + ] self.add_files(files) diff --git a/pySim/iso7816_4.py b/pySim/iso7816_4.py index ea838cf1..c1df419b 100644 --- a/pySim/iso7816_4.py +++ b/pySim/iso7816_4.py @@ -24,39 +24,57 @@ from pySim.filesystem import * from pySim.tlv import * # Table 91 + Section 8.2.1.2 + + class ApplicationId(BER_TLV_IE, tag=0x4f): _construct = GreedyBytes # Table 91 + + class ApplicationLabel(BER_TLV_IE, tag=0x50): _construct = GreedyBytes # Table 91 + Section 5.3.1.2 + + class FileReference(BER_TLV_IE, tag=0x51): _construct = GreedyBytes # Table 91 + + class CommandApdu(BER_TLV_IE, tag=0x52): _construct = GreedyBytes # Table 91 + + class DiscretionaryData(BER_TLV_IE, tag=0x53): _construct = GreedyBytes # Table 91 + + class DiscretionaryTemplate(BER_TLV_IE, tag=0x73): _construct = GreedyBytes # Table 91 + RFC1738 / RFC2396 + + class URL(BER_TLV_IE, tag=0x5f50): _construct = GreedyString('ascii') # Table 91 + + class ApplicationRelatedDOSet(BER_TLV_IE, tag=0x61): _construct = GreedyBytes # Section 8.2.1.3 Application Template + + class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationId, ApplicationLabel, FileReference, - CommandApdu, DiscretionaryData, DiscretionaryTemplate,URL, + CommandApdu, DiscretionaryData, DiscretionaryTemplate, URL, ApplicationRelatedDOSet]): pass diff --git a/pySim/jsonpath.py b/pySim/jsonpath.py index 98dbd75d..4dd838c0 100644 --- a/pySim/jsonpath.py +++ b/pySim/jsonpath.py @@ -25,6 +25,7 @@ of a file or record in its JSON representation. # You should have received a copy of the GNU General Public License # along with this program. If not, see . + def js_path_find(js_dict, js_path): """Find/Match a JSON path within a given JSON-serializable dict. Args: @@ -35,6 +36,7 @@ def js_path_find(js_dict, js_path): jsonpath_expr = jsonpath_ng.parse(js_path) return jsonpath_expr.find(js_dict) + def js_path_modify(js_dict, js_path, new_val): """Find/Match a JSON path within a given JSON-serializable dict. Args: @@ -45,4 +47,3 @@ def js_path_modify(js_dict, js_path, new_val): jsonpath_expr = jsonpath_ng.parse(js_path) jsonpath_expr.find(js_dict) jsonpath_expr.update(js_dict, new_val) - diff --git a/pySim/profile.py b/pySim/profile.py index 8f3e986c..d5df083e 100644 --- a/pySim/profile.py +++ b/pySim/profile.py @@ -27,122 +27,126 @@ from pySim.utils import all_subclasses import abc import operator -def _mf_select_test(scc:SimCardCommands, cla_byte:str, sel_ctrl:str) -> bool: - cla_byte_bak = scc.cla_byte - sel_ctrl_bak = scc.sel_ctrl - scc.reset_card() - scc.cla_byte = cla_byte - scc.sel_ctrl = sel_ctrl - rc = True - try: - scc.select_file('3f00') - except: - rc = False +def _mf_select_test(scc: SimCardCommands, cla_byte: str, sel_ctrl: str) -> bool: + cla_byte_bak = scc.cla_byte + sel_ctrl_bak = scc.sel_ctrl + scc.reset_card() - scc.reset_card() - scc.cla_byte = cla_byte_bak - scc.sel_ctrl = sel_ctrl_bak - return rc + scc.cla_byte = cla_byte + scc.sel_ctrl = sel_ctrl + rc = True + try: + scc.select_file('3f00') + except: + rc = False -def match_uicc(scc:SimCardCommands) -> bool: - """ Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the - card is considered a UICC card. - """ - return _mf_select_test(scc, "00", "0004") + scc.reset_card() + scc.cla_byte = cla_byte_bak + scc.sel_ctrl = sel_ctrl_bak + return rc + + +def match_uicc(scc: SimCardCommands) -> bool: + """ Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the + card is considered a UICC card. + """ + return _mf_select_test(scc, "00", "0004") + + +def match_sim(scc: SimCardCommands) -> bool: + """ Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card + is also a simcard. This will be the case for most UICC cards, but there may + also be plain UICC cards without 2G support as well. + """ + return _mf_select_test(scc, "a0", "0000") -def match_sim(scc:SimCardCommands) -> bool: - """ Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card - is also a simcard. This will be the case for most UICC cards, but there may - also be plain UICC cards without 2G support as well. - """ - return _mf_select_test(scc, "a0", "0000") class CardProfile(object): - """A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of - applications as well as profile-specific SW and shell commands. Every card has - one card profile, but there may be multiple applications within that profile.""" + """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 + one card profile, but there may be multiple applications within that profile.""" - def __init__(self, name, **kw): - """ - Args: - desc (str) : Description - files_in_mf : List of CardEF instances present in MF - applications : List of CardApplications present on card - sw : List of status word definitions - shell_cmdsets : List of cmd2 shell command sets of profile-specific commands - cla : class byte that should be used with cards of this profile - sel_ctrl : selection control bytes class byte that should be used with cards of this profile - """ - self.name = name - self.desc = kw.get("desc", None) - self.files_in_mf = kw.get("files_in_mf", []) - self.sw = kw.get("sw", {}) - self.applications = kw.get("applications", []) - self.shell_cmdsets = kw.get("shell_cmdsets", []) - self.cla = kw.get("cla", "00") - self.sel_ctrl = kw.get("sel_ctrl", "0004") + def __init__(self, name, **kw): + """ + Args: + desc (str) : Description + files_in_mf : List of CardEF instances present in MF + applications : List of CardApplications present on card + sw : List of status word definitions + shell_cmdsets : List of cmd2 shell command sets of profile-specific commands + cla : class byte that should be used with cards of this profile + sel_ctrl : selection control bytes class byte that should be used with cards of this profile + """ + self.name = name + self.desc = kw.get("desc", None) + self.files_in_mf = kw.get("files_in_mf", []) + self.sw = kw.get("sw", {}) + self.applications = kw.get("applications", []) + self.shell_cmdsets = kw.get("shell_cmdsets", []) + self.cla = kw.get("cla", "00") + self.sel_ctrl = kw.get("sel_ctrl", "0004") - def __str__(self): - return self.name + def __str__(self): + return self.name - def add_application(self, app:CardApplication): - """Add an application to a card profile. + def add_application(self, app: CardApplication): + """Add an application to a card profile. - Args: - app : CardApplication instance to be added to profile - """ - self.applications.append(app) + Args: + app : CardApplication instance to be added to profile + """ + self.applications.append(app) - def interpret_sw(self, sw:str): - """Interpret a given status word within the profile. + def interpret_sw(self, sw: str): + """Interpret a given status word within the profile. - Args: - sw : Status word as string of 4 hex digits + Args: + sw : Status word as string of 4 hex digits - Returns: - Tuple of two strings - """ - return interpret_sw(self.sw, sw) + Returns: + Tuple of two strings + """ + return interpret_sw(self.sw, sw) - @staticmethod - def decode_select_response(data_hex:str) -> object: - """Decode the response to a SELECT command. + @staticmethod + def decode_select_response(data_hex: str) -> object: + """Decode the response to a SELECT command. - This is the fall-back method which doesn't perform any decoding. It mostly - exists so specific derived classes can overload it for actual decoding. - This method is implemented in the profile and is only used when application - specific decoding cannot be performed (no ADF is selected). + This is the fall-back method which doesn't perform any decoding. It mostly + exists so specific derived classes can overload it for actual decoding. + This method is implemented in the profile and is only used when application + specific decoding cannot be performed (no ADF is selected). - Args: - data_hex: Hex string of the select response - """ - return data_hex + Args: + data_hex: Hex string of the select response + """ + return data_hex - @staticmethod - @abc.abstractmethod - def match_with_card(scc:SimCardCommands) -> bool: - """Check if the specific profile matches the card. This method is a - placeholder that is overloaded by specific dirived classes. The method - actively probes the card to make sure the profile class matches the - physical card. This usually also means that the card is reset during - the process, so this method must not be called at random times. It may - only be called on startup. + @staticmethod + @abc.abstractmethod + def match_with_card(scc: SimCardCommands) -> bool: + """Check if the specific profile matches the card. This method is a + placeholder that is overloaded by specific dirived classes. The method + actively probes the card to make sure the profile class matches the + physical card. This usually also means that the card is reset during + the process, so this method must not be called at random times. It may + only be called on startup. - Args: - scc: SimCardCommands class - Returns: - match = True, no match = False - """ - return False + Args: + scc: SimCardCommands class + Returns: + match = True, no match = False + """ + return False - @staticmethod - def pick(scc:SimCardCommands): - profiles = list(all_subclasses(CardProfile)) - profiles.sort(key=operator.attrgetter('ORDER')) + @staticmethod + def pick(scc: SimCardCommands): + profiles = list(all_subclasses(CardProfile)) + profiles.sort(key=operator.attrgetter('ORDER')) - for p in profiles: - if p.match_with_card(scc): - return p() + for p in profiles: + if p.match_with_card(scc): + return p() - return None + return None diff --git a/pySim/sysmocom_sja2.py b/pySim/sysmocom_sja2.py index 6d49572a..f87e45a1 100644 --- a/pySim/sysmocom_sja2.py +++ b/pySim/sysmocom_sja2.py @@ -43,21 +43,23 @@ mac_length = { 1: 4 } + class EF_PIN(TransparentEF): def __init__(self, fid, name): super().__init__(fid, name=name, desc='%s PIN file' % name) + def _decode_bin(self, raw_bin_data): u = unpack('!BBB8s', raw_bin_data[:11]) - res = {'enabled': (True, False)[u[0] & 0x01], - 'initialized': (True, False)[u[0] & 0x02], - 'disable_able': (False, True)[u[0] & 0x10], - 'unblock_able': (False, True)[u[0] & 0x20], - 'change_able': (False, True)[u[0] & 0x40], - 'valid': (False, True)[u[0] & 0x80], - 'attempts_remaining': u[1], - 'maximum_attempts': u[2], - 'pin': u[3].hex(), - } + res = {'enabled': (True, False)[u[0] & 0x01], + 'initialized': (True, False)[u[0] & 0x02], + 'disable_able': (False, True)[u[0] & 0x10], + 'unblock_able': (False, True)[u[0] & 0x20], + 'change_able': (False, True)[u[0] & 0x40], + 'valid': (False, True)[u[0] & 0x80], + 'attempts_remaining': u[1], + 'maximum_attempts': u[2], + 'pin': u[3].hex(), + } if len(raw_bin_data) == 21: u2 = unpack('!BB8s', raw_bin_data[11:10]) res['attempts_remaining_puk'] = u2[0] @@ -65,9 +67,11 @@ class EF_PIN(TransparentEF): res['puk'] = u2[2].hex() return res + class EF_MILENAGE_CFG(TransparentEF): def __init__(self, fid='6f21', name='EF.MILENAGE_CFG', desc='Milenage connfiguration'): super().__init__(fid, name=name, desc=desc) + def _decode_bin(self, raw_bin_data): u = unpack('!BBBBB16s16s16s16s16s', raw_bin_data) return {'r1': u[0], 'r2': u[1], 'r3': u[2], 'r4': u[3], 'r5': u[4], @@ -76,11 +80,13 @@ class EF_MILENAGE_CFG(TransparentEF): 'c3': u[7].hex(), 'c4': u[8].hex(), 'c5': u[9].hex(), - } + } + class EF_0348_KEY(LinFixedEF): def __init__(self, fid='6f22', name='EF.0348_KEY', desc='TS 03.48 OTA Keys'): - super().__init__(fid, name=name, desc=desc, rec_len={27,35}) + super().__init__(fid, name=name, desc=desc, rec_len={27, 35}) + def _decode_record_bin(self, raw_bin_data): u = unpack('!BBB', raw_bin_data[0:3]) key_algo = (u[2] >> 6) & 1 @@ -92,34 +98,42 @@ class EF_0348_KEY(LinFixedEF): 'algorithm': key_algo2str[key_algo], 'mac_length': mac_length[(u[2] >> 7)], 'key': raw_bin_data[3:key_length].hex() - } + } + class EF_0348_COUNT(LinFixedEF): def __init__(self, fid='6f23', name='EF.0348_COUNT', desc='TS 03.48 OTA Counters'): - super().__init__(fid, name=name, desc=desc, rec_len={7,7}) + super().__init__(fid, name=name, desc=desc, rec_len={7, 7}) + def _decode_record_bin(self, raw_bin_data): u = unpack('!BB5s', raw_bin_data) return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2]} + class EF_SIM_AUTH_COUNTER(TransparentEF): def __init__(self, fid='af24', name='EF.SIM_AUTH_COUNTER'): super().__init__(fid, name=name, desc='Number of remaining RUN GSM ALGORITHM executions') self._construct = Struct('num_run_gsm_algo_remain'/Int32ub) + class EF_GP_COUNT(LinFixedEF): def __init__(self, fid='6f26', name='EF.GP_COUNT', desc='GP SCP02 Counters'): - super().__init__(fid, name=name, desc=desc, rec_len={5,5}) + super().__init__(fid, name=name, desc=desc, rec_len={5, 5}) + def _decode_record_bin(self, raw_bin_data): u = unpack('!BBHB', raw_bin_data) return {'sec_domain': u[0], 'key_set_version': u[1], 'counter': u[2], 'rfu': u[3]} + class EF_GP_DIV_DATA(LinFixedEF): def __init__(self, fid='6f27', name='EF.GP_DIV_DATA', desc='GP SCP02 key diversification data'): - super().__init__(fid, name=name, desc=desc, rec_len={12,12}) + super().__init__(fid, name=name, desc=desc, rec_len={12, 12}) + def _decode_record_bin(self, raw_bin_data): u = unpack('!BB8s', raw_bin_data) return {'sec_domain': u[0], 'key_set_version': u[1], 'key_div_data': u[2].hex()} + class EF_SIM_AUTH_KEY(TransparentEF): def __init__(self, fid='6f20', name='EF.SIM_AUTH_KEY'): super().__init__(fid, name=name, desc='USIM authentication key') @@ -129,10 +143,15 @@ class EF_SIM_AUTH_KEY(TransparentEF): 'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3)) self._construct = Struct('cfg'/CfgByte, 'key'/Bytes(16), - 'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)), - 'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16)) + 'op' / + If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes( + 16)), + 'opc' / + If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes( + 16)) ) + class DF_SYSTEM(CardDF): def __init__(self): super().__init__(fid='a515', name='DF.SYSTEM', desc='CardOS specifics') @@ -150,12 +169,13 @@ class DF_SYSTEM(CardDF): EF_0348_COUNT(), EF_GP_COUNT(), EF_GP_DIV_DATA(), - ] + ] self.add_files(files) def decode_select_response(self, resp_hex): return pySim.ts_102_221.CardProfileUICC.decode_select_response(resp_hex) + class EF_USIM_SQN(TransparentEF): def __init__(self, fid='af30', name='EF.USIM_SQN'): super().__init__(fid, name=name, desc='SQN parameters for AKA') @@ -165,9 +185,11 @@ class EF_USIM_SQN(TransparentEF): Flag2 = BitStruct('rfu'/BitsRFU(5), 'dont_clear_amf_for_macs'/Bit, 'aus_concealed'/Bit, 'autn_concealed'/Bit) self._construct = Struct('flag1'/Flag1, 'flag2'/Flag2, - 'delta_max'/BytesInteger(6), 'age_limit'/BytesInteger(6), + 'delta_max' / + BytesInteger(6), 'age_limit'/BytesInteger(6), 'freshness'/GreedyRange(BytesInteger(6))) + class EF_USIM_AUTH_KEY(TransparentEF): def __init__(self, fid='af20', name='EF.USIM_AUTH_KEY'): super().__init__(fid, name=name, desc='USIM authentication key') @@ -177,9 +199,15 @@ class EF_USIM_AUTH_KEY(TransparentEF): 'algorithm'/Enum(Nibble, milenage=4, sha1_aka=5, xor=15)) self._construct = Struct('cfg'/CfgByte, 'key'/Bytes(16), - 'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)), - 'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16)) + 'op' / + If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes( + 16)), + 'opc' / + If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes( + 16)) ) + + class EF_USIM_AUTH_KEY_2G(TransparentEF): def __init__(self, fid='af22', name='EF.USIM_AUTH_KEY_2G'): super().__init__(fid, name=name, desc='USIM authentication key in 2G context') @@ -189,33 +217,42 @@ class EF_USIM_AUTH_KEY_2G(TransparentEF): 'algorithm'/Enum(Nibble, milenage=4, comp128v1=1, comp128v2=2, comp128v3=3)) self._construct = Struct('cfg'/CfgByte, 'key'/Bytes(16), - 'op'/If(this.cfg.algorithm=='milenage' and not this.cfg.use_opc_instead_of_op, Bytes(16)), - 'opc'/If(this.cfg.algorithm=='milenage' and this.cfg.use_opc_instead_of_op, Bytes(16)) + 'op' / + If(this.cfg.algorithm == 'milenage' and not this.cfg.use_opc_instead_of_op, Bytes( + 16)), + 'opc' / + If(this.cfg.algorithm == 'milenage' and this.cfg.use_opc_instead_of_op, Bytes( + 16)) ) + + class EF_GBA_SK(TransparentEF): def __init__(self, fid='af31', name='EF.GBA_SK'): super().__init__(fid, name=name, desc='Secret key for GBA key derivation') self._construct = GreedyBytes + class EF_GBA_REC_LIST(TransparentEF): def __init__(self, fid='af32', name='EF.GBA_REC_LIST'): super().__init__(fid, name=name, desc='Secret key for GBA key derivation') # integers representing record numbers in EF-GBANL self._construct = GreedyRange(Int8ub) + class EF_GBA_INT_KEY(LinFixedEF): def __init__(self, fid='af33', name='EF.GBA_INT_KEY'): - super().__init__(fid, name=name, desc='Secret key for GBA key derivation', rec_len={32,32}) + super().__init__(fid, name=name, + desc='Secret key for GBA key derivation', rec_len={32, 32}) self._construct = GreedyBytes - class SysmocomSJA2(CardModel): - _atrs = [ "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9", - "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2", - "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5" ] + _atrs = ["3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 30 34 05 4B A9", + "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 75 31 33 02 51 B2", + "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 4C 52 75 31 04 51 D5"] + @classmethod - def add_files(cls, rs:RuntimeState): + def add_files(cls, rs: RuntimeState): """Add sysmocom SJA2 specific files to given RuntimeState.""" rs.mf.add_file(DF_SYSTEM()) # optional USIM application @@ -228,7 +265,7 @@ class SysmocomSJA2(CardModel): EF_GBA_REC_LIST(), EF_GBA_INT_KEY(), EF_USIM_SQN(), - ] + ] usim_adf.add_files(files_adf_usim) # optional ISIM application if 'a0000000871004' in rs.mf.applications: @@ -237,5 +274,5 @@ class SysmocomSJA2(CardModel): EF_USIM_AUTH_KEY(name='EF.ISIM_AUTH_KEY'), EF_USIM_AUTH_KEY_2G(name='EF.ISIM_AUTH_KEY_2G'), EF_USIM_SQN(name='EF.ISIM_SQN'), - ] + ] isim_adf.add_files(files_adf_isim) diff --git a/pySim/tlv.py b/pySim/tlv.py index e1e28525..ba1aa9fe 100644 --- a/pySim/tlv.py +++ b/pySim/tlv.py @@ -32,6 +32,7 @@ from pySim.exceptions import * import inspect import abc + class TlvMeta(abc.ABCMeta): """Metaclass which we use to set some class variables at the time of defining a subclass. This allows us to create subclasses for each TLV/IE type, where the class represents fixed @@ -54,6 +55,7 @@ class TlvMeta(abc.ABCMeta): x.nested_collection_cls = cls return x + class TlvCollectionMeta(abc.ABCMeta): """Metaclass which we use to set some class variables at the time of defining a subclass. This allows us to create subclasses for each Collection type, where the class represents fixed @@ -72,6 +74,7 @@ class Transcodable(abc.ABC): * via a 'construct' object stored in a derived class' _construct variable, or * via a 'construct' object stored in an instance _construct variable, or * via a derived class' _{to,from}_bytes() methods.""" + def __init__(self): self.encoded = None self.decoded = None @@ -95,7 +98,7 @@ class Transcodable(abc.ABC): def _to_bytes(self): 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 in the internal state and return it.""" self.encoded = do @@ -110,9 +113,10 @@ class Transcodable(abc.ABC): return self.decoded # 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 + class IE(Transcodable, metaclass=TlvMeta): # we specify the metaclass so any downstream subclasses will automatically use it """Base class for various Information Elements. We understand the notion of a hierarchy @@ -146,7 +150,7 @@ class IE(Transcodable, metaclass=TlvMeta): v = self.decoded 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. If this is a nested IE, the child IE instance list is re-created.""" if self.nested_collection: @@ -177,7 +181,7 @@ class IE(Transcodable, metaclass=TlvMeta): else: 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.""" if self.nested_collection: self.children = self.nested_collection.from_bytes(do) @@ -188,6 +192,7 @@ class IE(Transcodable, metaclass=TlvMeta): class TLV_IE(IE): """Abstract base class for various TLV type Information Elements.""" + def __init__(self, **kwargs): super().__init__(**kwargs) @@ -197,12 +202,12 @@ class TLV_IE(IE): @classmethod @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.""" @classmethod @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.""" @abc.abstractmethod @@ -210,7 +215,7 @@ class TLV_IE(IE): """Encode the tag part. Must be provided by derived (TLV format specific) class.""" @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 derived (TLV format specific) class.""" @@ -222,7 +227,7 @@ class TLV_IE(IE): val = self.to_bytes() 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) if rawtag: if rawtag != self.tag: @@ -240,50 +245,52 @@ class TLV_IE(IE): class BER_TLV_IE(TLV_IE): """TLV_IE formatted as ASN.1 BER described in ITU-T X.690 8.1.2.""" + def __init__(self, **kwargs): super().__init__(**kwargs) @classmethod - def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]: + def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]: return bertlv_parse_tag(do) @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) @classmethod - def _parse_len(cls, do:bytes) -> Tuple[int, bytes]: + def _parse_len(cls, do: bytes) -> Tuple[int, bytes]: return bertlv_parse_len(do) def _encode_tag(self) -> bytes: 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)) class COMPR_TLV_IE(TLV_IE): """TLV_IE formated as COMPREHENSION-TLV as described in ETSI TS 101 220.""" + def __init__(self, **kwargs): super().__init__(**kwargs) self.comprehension = False @classmethod - def _decode_tag(cls, do:bytes) -> Tuple[dict, bytes]: + def _decode_tag(cls, do: bytes) -> Tuple[dict, bytes]: return comprehensiontlv_parse_tag(do) @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) @classmethod - def _parse_len(cls, do:bytes) -> Tuple[int, bytes]: + def _parse_len(cls, do: bytes) -> Tuple[int, bytes]: return bertlv_parse_len(do) def _encode_tag(self) -> bytes: 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)) @@ -294,14 +301,15 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta): of each DO.""" # this is overridden by the TlvCollectionMeta metaclass, if it is used to create subclasses possible_nested = [] + def __init__(self, desc=None, **kwargs): self.desc = desc #print("possible_nested: ", self.possible_nested) self.members = kwargs.get('nested', self.possible_nested) self.members_by_tag = {} self.members_by_name = {} - 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_tag = {m.tag: 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 self.children = kwargs.get('children', []) self.encoded = None @@ -322,11 +330,11 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta): return TLV_IE_Collection(self.desc, nested=members) elif inspect.isclass(other) and issubclass(other, TLV_IE): # 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: 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. Args: binary : binary bytes of encoded data @@ -353,9 +361,9 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta): else: # unknown tag; create the related class on-the-fly using the same base class name = 'unknown_%s_%X' % (first.__base__.__name__, tag) - cls = type(name, (first.__base__,), {'tag':tag, 'possible_nested':[], - 'nested_collection_cls':None}) - cls._from_bytes = lambda s, a : {'raw': a.hex()} + cls = type(name, (first.__base__,), {'tag': tag, 'possible_nested': [], + 'nested_collection_cls': None}) + cls._from_bytes = lambda s, a: {'raw': a.hex()} cls._to_bytes = lambda s: bytes.fromhex(s.decoded['raw']) # create an instance and parse accordingly inst = cls() @@ -364,7 +372,7 @@ class TLV_IE_Collection(metaclass=TlvCollectionMeta): self.children = 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 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 diff --git a/pySim/transport/__init__.py b/pySim/transport/__init__.py index cb781b0b..9364b07e 100644 --- a/pySim/transport/__init__.py +++ b/pySim/transport/__init__.py @@ -29,217 +29,224 @@ from pySim.utils import sw_match, b2h, h2b, i2h # along with this program. If not, see . # -class ApduTracer: - def trace_command(self, cmd): - pass - def trace_response(self, cmd, sw, resp): - pass +class ApduTracer: + def trace_command(self, cmd): + pass + + def trace_response(self, cmd, sw, resp): + pass class LinkBase(abc.ABC): - """Base class for link/transport to card.""" + """Base class for link/transport to card.""" - def __init__(self, sw_interpreter=None, apdu_tracer=None): - self.sw_interpreter = sw_interpreter - self.apdu_tracer = apdu_tracer + def __init__(self, sw_interpreter=None, apdu_tracer=None): + self.sw_interpreter = sw_interpreter + self.apdu_tracer = apdu_tracer - @abc.abstractmethod - def _send_apdu_raw(self, pdu:str) -> Tuple[str, str]: - """Implementation specific method for sending the PDU.""" + @abc.abstractmethod + def _send_apdu_raw(self, pdu: str) -> Tuple[str, str]: + """Implementation specific method for sending the PDU.""" - def set_sw_interpreter(self, interp): - """Set an (optional) status word interpreter.""" - self.sw_interpreter = interp + def set_sw_interpreter(self, interp): + """Set an (optional) status word interpreter.""" + self.sw_interpreter = interp - @abc.abstractmethod - def wait_for_card(self, timeout:int=None, newcardonly:bool=False): - """Wait for a card and connect to it + @abc.abstractmethod + def wait_for_card(self, timeout: int = None, newcardonly: bool = False): + """Wait for a card and connect to it - Args: - timeout : Maximum wait time in seconds (None=no timeout) - newcardonly : Should we wait for a new card, or an already inserted one ? - """ + Args: + timeout : Maximum wait time in seconds (None=no timeout) + newcardonly : Should we wait for a new card, or an already inserted one ? + """ - @abc.abstractmethod - def connect(self): - """Connect to a card immediately - """ + @abc.abstractmethod + def connect(self): + """Connect to a card immediately + """ - @abc.abstractmethod - def disconnect(self): - """Disconnect from card - """ + @abc.abstractmethod + def disconnect(self): + """Disconnect from card + """ - @abc.abstractmethod - def reset_card(self): - """Resets the card (power down/up) - """ + @abc.abstractmethod + def reset_card(self): + """Resets the card (power down/up) + """ - def send_apdu_raw(self, pdu:str): - """Sends an APDU with minimal processing + def send_apdu_raw(self, pdu: str): + """Sends an APDU with minimal processing - Args: - pdu : string of hexadecimal characters (ex. "A0A40000023F00") - Returns: - tuple(data, sw), where - data : string (in hex) of returned data (ex. "074F4EFFFF") - sw : string (in hex) of status word (ex. "9000") - """ - if self.apdu_tracer: - self.apdu_tracer.trace_command(pdu) - (data, sw) = self._send_apdu_raw(pdu) - if self.apdu_tracer: - self.apdu_tracer.trace_response(pdu, sw, data) - return (data, sw) + Args: + pdu : string of hexadecimal characters (ex. "A0A40000023F00") + Returns: + tuple(data, sw), where + data : string (in hex) of returned data (ex. "074F4EFFFF") + sw : string (in hex) of status word (ex. "9000") + """ + if self.apdu_tracer: + self.apdu_tracer.trace_command(pdu) + (data, sw) = self._send_apdu_raw(pdu) + if self.apdu_tracer: + self.apdu_tracer.trace_response(pdu, sw, data) + return (data, sw) - def send_apdu(self, pdu): - """Sends an APDU and auto fetch response data + def send_apdu(self, pdu): + """Sends an APDU and auto fetch response data - Args: - pdu : string of hexadecimal characters (ex. "A0A40000023F00") - Returns: - tuple(data, sw), where - data : string (in hex) of returned data (ex. "074F4EFFFF") - sw : string (in hex) of status word (ex. "9000") - """ - data, sw = self.send_apdu_raw(pdu) + Args: + pdu : string of hexadecimal characters (ex. "A0A40000023F00") + Returns: + tuple(data, sw), where + data : string (in hex) of returned data (ex. "074F4EFFFF") + sw : string (in hex) of status word (ex. "9000") + """ + data, sw = self.send_apdu_raw(pdu) - # When whe have sent the first APDU, the SW may indicate that there are response bytes - # available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where - # xx is the number of response bytes available. - # See also: - if (sw is not None): - if ((sw[0:2] == '9f') or (sw[0:2] == '61')): - # SW1=9F: 3GPP TS 51.011 9.4.1, Responses to commands which are correctly executed - # SW1=61: ISO/IEC 7816-4, Table 5 — General meaning of the interindustry values of SW1-SW2 - pdu_gr = pdu[0:2] + 'c00000' + sw[2:4] - data, sw = self.send_apdu_raw(pdu_gr) - if sw[0:2] == '6c': - # SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding - pdu_gr = pdu[0:8] + sw[2:4] - data,sw = self.send_apdu_raw(pdu_gr) + # When we have sent the first APDU, the SW may indicate that there are response bytes + # available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where + # xx is the number of response bytes available. + # See also: + if (sw is not None): + if ((sw[0:2] == '9f') or (sw[0:2] == '61')): + # SW1=9F: 3GPP TS 51.011 9.4.1, Responses to commands which are correctly executed + # SW1=61: ISO/IEC 7816-4, Table 5 — General meaning of the interindustry values of SW1-SW2 + pdu_gr = pdu[0:2] + 'c00000' + sw[2:4] + data, sw = self.send_apdu_raw(pdu_gr) + if sw[0:2] == '6c': + # SW1=6C: ETSI TS 102 221 Table 7.1: Procedure byte coding + pdu_gr = pdu[0:8] + sw[2:4] + data, sw = self.send_apdu_raw(pdu_gr) - return data, sw + return data, sw - def send_apdu_checksw(self, pdu, sw="9000"): - """Sends an APDU and check returned SW + def send_apdu_checksw(self, pdu, sw="9000"): + """Sends an APDU and check returned SW - Args: - pdu : string of hexadecimal characters (ex. "A0A40000023F00") - sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain - digits using a '?' to add some ambiguity if needed. - Returns: - tuple(data, sw), where - data : string (in hex) of returned data (ex. "074F4EFFFF") - sw : string (in hex) of status word (ex. "9000") - """ - rv = self.send_apdu(pdu) + Args: + pdu : string of hexadecimal characters (ex. "A0A40000023F00") + sw : string of 4 hexadecimal characters (ex. "9000"). The user may mask out certain + digits using a '?' to add some ambiguity if needed. + Returns: + tuple(data, sw), where + data : string (in hex) of returned data (ex. "074F4EFFFF") + sw : string (in hex) of status word (ex. "9000") + """ + rv = self.send_apdu(pdu) - if sw == '9000' and sw_match(rv[1], '91xx'): - # proactive sim as per TS 102 221 Setion 7.4.2 - rv = self.send_apdu_checksw('80120000' + rv[1][2:], sw) - print("FETCH: %s", rv[0]) - if not sw_match(rv[1], sw): - raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter) - return rv + if sw == '9000' and sw_match(rv[1], '91xx'): + # proactive sim as per TS 102 221 Setion 7.4.2 + rv = self.send_apdu_checksw('80120000' + rv[1][2:], sw) + print("FETCH: %s", rv[0]) + if not sw_match(rv[1], sw): + raise SwMatchError(rv[1], sw.lower(), self.sw_interpreter) + return rv - def send_apdu_constr(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr): - """Build and sends an APDU using a 'construct' definition; parses response. + def send_apdu_constr(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr): + """Build and sends an APDU using a 'construct' definition; parses response. - Args: - cla : string (in hex) ISO 7816 class byte - ins : string (in hex) ISO 7816 instruction byte - p1 : string (in hex) ISO 7116 Parameter 1 byte - p2 : string (in hex) ISO 7116 Parameter 2 byte - cmd_cosntr : defining how to generate binary APDU command data - cmd_data : command data passed to cmd_constr - resp_cosntr : defining how to decode binary APDU response data - Returns: - Tuple of (decoded_data, sw) - """ - cmd = cmd_constr.build(cmd_data) if cmd_data else '' - p3 = i2h([len(cmd)]) - pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)]) - (data, sw) = self.send_apdu(pdu) - if data: - # filter the resulting dict to avoid '_io' members inside - rsp = filter_dict(resp_constr.parse(h2b(data))) - else: - rsp = None - return (rsp, sw) + Args: + cla : string (in hex) ISO 7816 class byte + ins : string (in hex) ISO 7816 instruction byte + p1 : string (in hex) ISO 7116 Parameter 1 byte + p2 : string (in hex) ISO 7116 Parameter 2 byte + cmd_cosntr : defining how to generate binary APDU command data + cmd_data : command data passed to cmd_constr + resp_cosntr : defining how to decode binary APDU response data + Returns: + Tuple of (decoded_data, sw) + """ + cmd = cmd_constr.build(cmd_data) if cmd_data else '' + p3 = i2h([len(cmd)]) + pdu = ''.join([cla, ins, p1, p2, p3, b2h(cmd)]) + (data, sw) = self.send_apdu(pdu) + if data: + # filter the resulting dict to avoid '_io' members inside + rsp = filter_dict(resp_constr.parse(h2b(data))) + else: + rsp = None + return (rsp, sw) - def send_apdu_constr_checksw(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr, - sw_exp="9000"): - """Build and sends an APDU using a 'construct' definition; parses response. + def send_apdu_constr_checksw(self, cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr, + sw_exp="9000"): + """Build and sends an APDU using a 'construct' definition; parses response. + + Args: + cla : string (in hex) ISO 7816 class byte + ins : string (in hex) ISO 7816 instruction byte + p1 : string (in hex) ISO 7116 Parameter 1 byte + p2 : string (in hex) ISO 7116 Parameter 2 byte + cmd_cosntr : defining how to generate binary APDU command data + cmd_data : command data passed to cmd_constr + resp_cosntr : defining how to decode binary APDU response data + exp_sw : string (in hex) of status word (ex. "9000") + Returns: + Tuple of (decoded_data, sw) + """ + (rsp, sw) = self.send_apdu_constr(cla, ins, + p1, p2, cmd_constr, cmd_data, resp_constr) + if not sw_match(sw, sw_exp): + raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter) + return (rsp, sw) - Args: - cla : string (in hex) ISO 7816 class byte - ins : string (in hex) ISO 7816 instruction byte - p1 : string (in hex) ISO 7116 Parameter 1 byte - p2 : string (in hex) ISO 7116 Parameter 2 byte - cmd_cosntr : defining how to generate binary APDU command data - cmd_data : command data passed to cmd_constr - resp_cosntr : defining how to decode binary APDU response data - exp_sw : string (in hex) of status word (ex. "9000") - Returns: - Tuple of (decoded_data, sw) - """ - (rsp, sw) = self.send_apdu_constr(cla, ins, p1, p2, cmd_constr, cmd_data, resp_constr) - if not sw_match(sw, sw_exp): - raise SwMatchError(sw, sw_exp.lower(), self.sw_interpreter) - return (rsp, sw) def argparse_add_reader_args(arg_parser): - """Add all reader related arguments to the given argparse.Argumentparser instance.""" - serial_group = arg_parser.add_argument_group('Serial Reader') - serial_group.add_argument('-d', '--device', metavar='DEV', default='/dev/ttyUSB0', - help='Serial Device for SIM access') - serial_group.add_argument('-b', '--baud', dest='baudrate', type=int, metavar='BAUD', default=9600, - help='Baud rate used for SIM access') + """Add all reader related arguments to the given argparse.Argumentparser instance.""" + serial_group = arg_parser.add_argument_group('Serial Reader') + serial_group.add_argument('-d', '--device', metavar='DEV', default='/dev/ttyUSB0', + help='Serial Device for SIM access') + serial_group.add_argument('-b', '--baud', dest='baudrate', type=int, metavar='BAUD', default=9600, + help='Baud rate used for SIM access') - pcsc_group = arg_parser.add_argument_group('PC/SC Reader') - pcsc_group.add_argument('-p', '--pcsc-device', type=int, dest='pcsc_dev', metavar='PCSC', default=None, - help='PC/SC reader number to use for SIM access') + pcsc_group = arg_parser.add_argument_group('PC/SC Reader') + pcsc_group.add_argument('-p', '--pcsc-device', type=int, dest='pcsc_dev', metavar='PCSC', default=None, + help='PC/SC reader number to use for SIM access') - modem_group = arg_parser.add_argument_group('AT Command Modem Reader') - modem_group.add_argument('--modem-device', dest='modem_dev', metavar='DEV', default=None, - help='Serial port of modem for Generic SIM Access (3GPP TS 27.007)') - modem_group.add_argument('--modem-baud', type=int, metavar='BAUD', default=115200, - help='Baud rate used for modem port') + modem_group = arg_parser.add_argument_group('AT Command Modem Reader') + modem_group.add_argument('--modem-device', dest='modem_dev', metavar='DEV', default=None, + help='Serial port of modem for Generic SIM Access (3GPP TS 27.007)') + modem_group.add_argument('--modem-baud', type=int, metavar='BAUD', default=115200, + help='Baud rate used for modem port') - osmobb_group = arg_parser.add_argument_group('OsmocomBB Reader') - osmobb_group.add_argument('--osmocon', dest='osmocon_sock', metavar='PATH', default=None, - help='Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)') + osmobb_group = arg_parser.add_argument_group('OsmocomBB Reader') + osmobb_group.add_argument('--osmocon', dest='osmocon_sock', metavar='PATH', default=None, + help='Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)') + + return arg_parser - return arg_parser def init_reader(opts, **kwargs) -> Optional[LinkBase]: - """ - Init card reader driver - """ - sl = None # type : :Optional[LinkBase] - try: - if opts.pcsc_dev is not None: - print("Using PC/SC reader interface") - from pySim.transport.pcsc import PcscSimLink - sl = PcscSimLink(opts.pcsc_dev, **kwargs) - elif opts.osmocon_sock is not None: - print("Using Calypso-based (OsmocomBB) reader interface") - from pySim.transport.calypso import CalypsoSimLink - sl = CalypsoSimLink(sock_path=opts.osmocon_sock, **kwargs) - elif opts.modem_dev is not None: - print("Using modem for Generic SIM Access (3GPP TS 27.007)") - from pySim.transport.modem_atcmd import ModemATCommandLink - sl = ModemATCommandLink(device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs) - else: # Serial reader is default - print("Using serial reader interface") - from pySim.transport.serial import SerialSimLink - sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate, **kwargs) - return sl - except Exception as e: - if str(e): - print("Card reader initialization failed with exception:\n" + str(e)) - else: - print("Card reader initialization failed with an exception of type:\n" + str(type(e))) - return None + """ + Init card reader driver + """ + sl = None # type : :Optional[LinkBase] + try: + if opts.pcsc_dev is not None: + print("Using PC/SC reader interface") + from pySim.transport.pcsc import PcscSimLink + sl = PcscSimLink(opts.pcsc_dev, **kwargs) + elif opts.osmocon_sock is not None: + print("Using Calypso-based (OsmocomBB) reader interface") + from pySim.transport.calypso import CalypsoSimLink + sl = CalypsoSimLink(sock_path=opts.osmocon_sock, **kwargs) + elif opts.modem_dev is not None: + print("Using modem for Generic SIM Access (3GPP TS 27.007)") + from pySim.transport.modem_atcmd import ModemATCommandLink + sl = ModemATCommandLink( + device=opts.modem_dev, baudrate=opts.modem_baud, **kwargs) + else: # Serial reader is default + print("Using serial reader interface") + from pySim.transport.serial import SerialSimLink + sl = SerialSimLink(device=opts.device, + baudrate=opts.baudrate, **kwargs) + return sl + except Exception as e: + if str(e): + print("Card reader initialization failed with exception:\n" + str(e)) + else: + print( + "Card reader initialization failed with an exception of type:\n" + str(type(e))) + return None diff --git a/pySim/transport/calypso.py b/pySim/transport/calypso.py index b55a089a..15c9e5f6 100644 --- a/pySim/transport/calypso.py +++ b/pySim/transport/calypso.py @@ -25,127 +25,132 @@ from pySim.transport import LinkBase from pySim.exceptions import * from pySim.utils import h2b, b2h + class L1CTLMessage(object): - # Every (encoded) L1CTL message has the following structure: - # - msg_length (2 bytes, net order) - # - l1ctl_hdr (packed structure) - # - msg_type - # - flags - # - padding (2 spare bytes) - # - ... payload ... + # Every (encoded) L1CTL message has the following structure: + # - msg_length (2 bytes, net order) + # - l1ctl_hdr (packed structure) + # - msg_type + # - flags + # - padding (2 spare bytes) + # - ... payload ... - def __init__(self, msg_type, flags = 0x00): - # Init L1CTL message header - self.data = struct.pack("BBxx", msg_type, flags) + def __init__(self, msg_type, flags=0x00): + # Init L1CTL message header + self.data = struct.pack("BBxx", msg_type, flags) + + def gen_msg(self): + return struct.pack("!H", len(self.data)) + self.data - def gen_msg(self): - return struct.pack("!H", len(self.data)) + self.data class L1CTLMessageReset(L1CTLMessage): - # L1CTL message types - L1CTL_RESET_REQ = 0x0d - L1CTL_RESET_IND = 0x07 - L1CTL_RESET_CONF = 0x0e + # L1CTL message types + L1CTL_RESET_REQ = 0x0d + L1CTL_RESET_IND = 0x07 + L1CTL_RESET_CONF = 0x0e - # Reset types - L1CTL_RES_T_BOOT = 0x00 - L1CTL_RES_T_FULL = 0x01 - L1CTL_RES_T_SCHED = 0x02 + # Reset types + L1CTL_RES_T_BOOT = 0x00 + L1CTL_RES_T_FULL = 0x01 + L1CTL_RES_T_SCHED = 0x02 + + def __init__(self, type=L1CTL_RES_T_FULL): + super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ) + self.data += struct.pack("Bxxx", type) - def __init__(self, type = L1CTL_RES_T_FULL): - super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ) - self.data += struct.pack("Bxxx", type) class L1CTLMessageSIM(L1CTLMessage): - # SIM related message types - L1CTL_SIM_REQ = 0x16 - L1CTL_SIM_CONF = 0x17 + # SIM related message types + L1CTL_SIM_REQ = 0x16 + L1CTL_SIM_CONF = 0x17 + + def __init__(self, pdu): + super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ) + self.data += pdu - def __init__(self, pdu): - super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ) - self.data += pdu 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): - super().__init__(**kwargs) - # Make sure that a given socket path exists - if not os.path.exists(sock_path): - raise ReaderError("There is no such ('%s') UNIX socket" % sock_path) + def __init__(self, sock_path: str = "/tmp/osmocom_l2", **kwargs): + super().__init__(**kwargs) + # Make sure that a given socket path exists + if not os.path.exists(sock_path): + raise ReaderError( + "There is no such ('%s') UNIX socket" % sock_path) - print("Connecting to osmocon at '%s'..." % sock_path) + print("Connecting to osmocon at '%s'..." % sock_path) - # Establish a client connection - self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - self.sock.connect(sock_path) + # Establish a client connection + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self.sock.connect(sock_path) - def __del__(self): - self.sock.close() + def __del__(self): + self.sock.close() - def wait_for_rsp(self, exp_len = 128): - # Wait for incoming data (timeout is 3 seconds) - s, _, _ = select.select([self.sock], [], [], 3.0) - if not s: - raise ReaderError("Timeout waiting for card response") + def wait_for_rsp(self, exp_len=128): + # Wait for incoming data (timeout is 3 seconds) + s, _, _ = select.select([self.sock], [], [], 3.0) + if not s: + raise ReaderError("Timeout waiting for card response") - # Receive expected amount of bytes from osmocon - rsp = self.sock.recv(exp_len) - return rsp + # Receive expected amount of bytes from osmocon + rsp = self.sock.recv(exp_len) + return rsp - def reset_card(self): - # Request FULL reset - req_msg = L1CTLMessageReset() - self.sock.send(req_msg.gen_msg()) + def reset_card(self): + # Request FULL reset + req_msg = L1CTLMessageReset() + self.sock.send(req_msg.gen_msg()) - # Wait for confirmation - rsp = self.wait_for_rsp() - rsp_msg = struct.unpack_from("!HB", rsp) - if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF: - raise ReaderError("Failed to reset Calypso PHY") + # Wait for confirmation + rsp = self.wait_for_rsp() + rsp_msg = struct.unpack_from("!HB", rsp) + if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF: + raise ReaderError("Failed to reset Calypso PHY") - def connect(self): - self.reset_card() + def connect(self): + self.reset_card() - def disconnect(self): - pass # Nothing to do really ... + def disconnect(self): + pass # Nothing to do really ... - def wait_for_card(self, timeout = None, newcardonly = False): - pass # Nothing to do really ... + def wait_for_card(self, timeout=None, newcardonly=False): + pass # Nothing to do really ... - def _send_apdu_raw(self, pdu): + def _send_apdu_raw(self, pdu): - # Request FULL reset - req_msg = L1CTLMessageSIM(h2b(pdu)) - self.sock.send(req_msg.gen_msg()) + # Request FULL reset + req_msg = L1CTLMessageSIM(h2b(pdu)) + self.sock.send(req_msg.gen_msg()) - # Read message length first - rsp = self.wait_for_rsp(struct.calcsize("!H")) - msg_len = struct.unpack_from("!H", rsp)[0] - if msg_len < struct.calcsize("BBxx"): - raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF") + # Read message length first + rsp = self.wait_for_rsp(struct.calcsize("!H")) + msg_len = struct.unpack_from("!H", rsp)[0] + if msg_len < struct.calcsize("BBxx"): + raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF") - # Read the whole message then - rsp = self.sock.recv(msg_len) + # Read the whole message then + rsp = self.sock.recv(msg_len) - # Verify L1CTL header - hdr = struct.unpack_from("BBxx", rsp) - if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF: - raise ReaderError("Unexpected L1CTL message received") + # Verify L1CTL header + hdr = struct.unpack_from("BBxx", rsp) + if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF: + raise ReaderError("Unexpected L1CTL message received") - # Verify the payload length - offset = struct.calcsize("BBxx") - if len(rsp) <= offset: - raise ProtocolError("Empty response from SIM?!?") + # Verify the payload length + offset = struct.calcsize("BBxx") + if len(rsp) <= offset: + raise ProtocolError("Empty response from SIM?!?") - # Omit L1CTL header - rsp = rsp[offset:] + # Omit L1CTL header + rsp = rsp[offset:] - # Unpack data and SW - data = rsp[:-2] - sw = rsp[-2:] + # Unpack data and SW + data = rsp[:-2] + sw = rsp[-2:] - return b2h(data), b2h(sw) + return b2h(data), b2h(sw) diff --git a/pySim/transport/modem_atcmd.py b/pySim/transport/modem_atcmd.py index 04f92210..a373d685 100644 --- a/pySim/transport/modem_atcmd.py +++ b/pySim/transport/modem_atcmd.py @@ -27,138 +27,142 @@ from pySim.exceptions import * # HACK: if somebody needs to debug this thing # log.root.setLevel(log.DEBUG) + class ModemATCommandLink(LinkBase): - """Transport Link for 3GPP TS 27.007 compliant modems.""" - def __init__(self, device:str='/dev/ttyUSB0', baudrate:int=115200, **kwargs): - super().__init__(**kwargs) - self._sl = serial.Serial(device, baudrate, timeout=5) - self._echo = False # this will be auto-detected by _check_echo() - self._device = device - self._atr = None + """Transport Link for 3GPP TS 27.007 compliant modems.""" - # Check the AT interface - self._check_echo() + def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 115200, **kwargs): + super().__init__(**kwargs) + self._sl = serial.Serial(device, baudrate, timeout=5) + self._echo = False # this will be auto-detected by _check_echo() + self._device = device + self._atr = None - # Trigger initial reset - self.reset_card() + # Check the AT interface + self._check_echo() - def __del__(self): - if hasattr(self, '_sl'): - self._sl.close() + # Trigger initial reset + self.reset_card() - def send_at_cmd(self, cmd, timeout=0.2, patience=0.002): - # Convert from string to bytes, if needed - bcmd = cmd if type(cmd) is bytes else cmd.encode() - bcmd += b'\r' + def __del__(self): + if hasattr(self, '_sl'): + self._sl.close() - # Clean input buffer from previous/unexpected data - self._sl.reset_input_buffer() + def send_at_cmd(self, cmd, timeout=0.2, patience=0.002): + # Convert from string to bytes, if needed + bcmd = cmd if type(cmd) is bytes else cmd.encode() + bcmd += b'\r' - # Send command to the modem - log.debug('Sending AT command: %s', cmd) - try: - wlen = self._sl.write(bcmd) - assert(wlen == len(bcmd)) - except: - raise ReaderError('Failed to send AT command: %s' % cmd) + # Clean input buffer from previous/unexpected data + self._sl.reset_input_buffer() - rsp = b'' - its = 1 - t_start = time.time() - while True: - rsp = rsp + self._sl.read(self._sl.in_waiting) - lines = rsp.split(b'\r\n') - if len(lines) >= 2: - res = lines[-2] - if res == b'OK': - log.debug('Command finished with result: %s', res) - break - if res == b'ERROR' or res.startswith(b'+CME ERROR:'): - log.error('Command failed with result: %s', res) - break + # Send command to the modem + log.debug('Sending AT command: %s', cmd) + try: + wlen = self._sl.write(bcmd) + assert(wlen == len(bcmd)) + except: + raise ReaderError('Failed to send AT command: %s' % cmd) - if time.time() - t_start >= timeout: - log.info('Command finished with timeout >= %ss', timeout) - break - time.sleep(patience) - its += 1 - log.debug('Command took %0.6fs (%d cycles a %fs)', time.time() - t_start, its, patience) + rsp = b'' + its = 1 + t_start = time.time() + while True: + rsp = rsp + self._sl.read(self._sl.in_waiting) + lines = rsp.split(b'\r\n') + if len(lines) >= 2: + res = lines[-2] + if res == b'OK': + log.debug('Command finished with result: %s', res) + break + if res == b'ERROR' or res.startswith(b'+CME ERROR:'): + log.error('Command failed with result: %s', res) + break - if self._echo: - # Skip echo chars - rsp = rsp[wlen:] - rsp = rsp.strip() - rsp = rsp.split(b'\r\n\r\n') + if time.time() - t_start >= timeout: + log.info('Command finished with timeout >= %ss', timeout) + break + time.sleep(patience) + its += 1 + log.debug('Command took %0.6fs (%d cycles a %fs)', + time.time() - t_start, its, patience) - log.debug('Got response from modem: %s', rsp) - return rsp + if self._echo: + # Skip echo chars + rsp = rsp[wlen:] + rsp = rsp.strip() + rsp = rsp.split(b'\r\n\r\n') - def _check_echo(self): - """Verify the correct response to 'AT' command - and detect if inputs are echoed by the device + log.debug('Got response from modem: %s', rsp) + return rsp - Although echo of inputs can be enabled/disabled via - ATE1/ATE0, respectively, we rather detect the current - configuration of the modem without any change. - """ - # Next command shall not strip the echo from the response - self._echo = False - result = self.send_at_cmd('AT') + def _check_echo(self): + """Verify the correct response to 'AT' command + and detect if inputs are echoed by the device - # Verify the response - if len(result) > 0: - if result[-1] == b'OK': - self._echo = False - return - elif result[-1] == b'AT\r\r\nOK': - self._echo = True - return - raise ReaderError('Interface \'%s\' does not respond to \'AT\' command' % self._device) + Although echo of inputs can be enabled/disabled via + ATE1/ATE0, respectively, we rather detect the current + configuration of the modem without any change. + """ + # Next command shall not strip the echo from the response + self._echo = False + result = self.send_at_cmd('AT') - def reset_card(self): - # Reset the modem, just to be sure - if self.send_at_cmd('ATZ') != [b'OK']: - raise ReaderError('Failed to reset the modem') + # Verify the response + if len(result) > 0: + if result[-1] == b'OK': + self._echo = False + return + elif result[-1] == b'AT\r\r\nOK': + self._echo = True + return + raise ReaderError( + 'Interface \'%s\' does not respond to \'AT\' command' % self._device) - # Make sure that generic SIM access is supported - if self.send_at_cmd('AT+CSIM=?') != [b'OK']: - raise ReaderError('The modem does not seem to support SIM access') + def reset_card(self): + # Reset the modem, just to be sure + if self.send_at_cmd('ATZ') != [b'OK']: + raise ReaderError('Failed to reset the modem') - log.info('Modem at \'%s\' is ready!' % self._device) + # Make sure that generic SIM access is supported + if self.send_at_cmd('AT+CSIM=?') != [b'OK']: + raise ReaderError('The modem does not seem to support SIM access') - def connect(self): - pass # Nothing to do really ... + log.info('Modem at \'%s\' is ready!' % self._device) - def disconnect(self): - pass # Nothing to do really ... + def connect(self): + pass # Nothing to do really ... - def wait_for_card(self, timeout=None, newcardonly=False): - pass # Nothing to do really ... + def disconnect(self): + pass # Nothing to do really ... - def _send_apdu_raw(self, pdu): - # Make sure pdu has upper case hex digits [A-F] - pdu = pdu.upper() + def wait_for_card(self, timeout=None, newcardonly=False): + pass # Nothing to do really ... - # Prepare the command as described in 8.17 - cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu) - log.debug('Sending command: %s', cmd) + def _send_apdu_raw(self, pdu): + # Make sure pdu has upper case hex digits [A-F] + pdu = pdu.upper() - # Send AT+CSIM command to the modem - # TODO: also handle +CME ERROR: - rsp = self.send_at_cmd(cmd) - if len(rsp) != 2 or rsp[-1] != b'OK': - raise ReaderError('APDU transfer failed: %s' % str(rsp)) - rsp = rsp[0] # Get rid of b'OK' + # Prepare the command as described in 8.17 + cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu) + log.debug('Sending command: %s', cmd) - # Make sure that the response has format: b'+CSIM: %d,\"%s\"' - try: - result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp) - (rsp_pdu_len, rsp_pdu) = result.groups() - except: - raise ReaderError('Failed to parse response from modem: %s' % rsp) + # Send AT+CSIM command to the modem + # TODO: also handle +CME ERROR: + rsp = self.send_at_cmd(cmd) + if len(rsp) != 2 or rsp[-1] != b'OK': + raise ReaderError('APDU transfer failed: %s' % str(rsp)) + rsp = rsp[0] # Get rid of b'OK' - # TODO: make sure we have at least SW - data = rsp_pdu[:-4].decode().lower() - sw = rsp_pdu[-4:].decode().lower() - log.debug('Command response: %s, %s', data, sw) - return data, sw + # Make sure that the response has format: b'+CSIM: %d,\"%s\"' + try: + result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp) + (rsp_pdu_len, rsp_pdu) = result.groups() + except: + raise ReaderError('Failed to parse response from modem: %s' % rsp) + + # TODO: make sure we have at least SW + data = rsp_pdu[:-4].decode().lower() + sw = rsp_pdu[-4:].decode().lower() + log.debug('Command response: %s, %s', data, sw) + return data, sw diff --git a/pySim/transport/pcsc.py b/pySim/transport/pcsc.py index 147a6b7a..ca1751ab 100644 --- a/pySim/transport/pcsc.py +++ b/pySim/transport/pcsc.py @@ -28,63 +28,64 @@ from pySim.utils import h2i, i2h class PcscSimLink(LinkBase): - """ pySim: PCSC reader transport link.""" + """ pySim: PCSC reader transport link.""" - def __init__(self, reader_number:int=0, **kwargs): - super().__init__(**kwargs) - r = readers() - if reader_number >= len(r): - raise ReaderError - self._reader = r[reader_number] - self._con = self._reader.createConnection() + def __init__(self, reader_number: int = 0, **kwargs): + super().__init__(**kwargs) + r = readers() + if reader_number >= len(r): + raise ReaderError + self._reader = r[reader_number] + self._con = self._reader.createConnection() - def __del__(self): - try: - # FIXME: this causes multiple warnings in Python 3.5.3 - self._con.disconnect() - except: - pass - return + def __del__(self): + try: + # FIXME: this causes multiple warnings in Python 3.5.3 + self._con.disconnect() + except: + pass + return - def wait_for_card(self, timeout:int=None, newcardonly:bool=False): - cr = CardRequest(readers=[self._reader], timeout=timeout, newcardonly=newcardonly) - try: - cr.waitforcard() - except CardRequestTimeoutException: - raise NoCardError() - self.connect() + def wait_for_card(self, timeout: int = None, newcardonly: bool = False): + cr = CardRequest(readers=[self._reader], + timeout=timeout, newcardonly=newcardonly) + try: + cr.waitforcard() + except CardRequestTimeoutException: + raise NoCardError() + self.connect() - def connect(self): - try: - # To avoid leakage of resources, make sure the reader - # is disconnected - self.disconnect() + def connect(self): + try: + # To avoid leakage of resources, make sure the reader + # is disconnected + self.disconnect() - # Explicitly select T=0 communication protocol - self._con.connect(CardConnection.T0_protocol) - except CardConnectionException: - raise ProtocolError() - except NoCardException: - raise NoCardError() + # Explicitly select T=0 communication protocol + self._con.connect(CardConnection.T0_protocol) + except CardConnectionException: + raise ProtocolError() + except NoCardException: + raise NoCardError() - def get_atr(self): - return self._con.getATR() + def get_atr(self): + return self._con.getATR() - def disconnect(self): - self._con.disconnect() + def disconnect(self): + self._con.disconnect() - def reset_card(self): - self.disconnect() - self.connect() - return 1 + def reset_card(self): + self.disconnect() + self.connect() + return 1 - def _send_apdu_raw(self, pdu): + def _send_apdu_raw(self, pdu): - apdu = h2i(pdu) + apdu = h2i(pdu) - data, sw1, sw2 = self._con.transmit(apdu) + data, sw1, sw2 = self._con.transmit(apdu) - sw = [sw1, sw2] + sw = [sw1, sw2] - # Return value - return i2h(data), i2h(sw) + # Return value + return i2h(data), i2h(sw) diff --git a/pySim/transport/serial.py b/pySim/transport/serial.py index c0809262..924df9ec 100644 --- a/pySim/transport/serial.py +++ b/pySim/transport/serial.py @@ -26,209 +26,211 @@ from pySim.utils import h2b, b2h 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', - debug:bool=False, **kwargs): - super().__init__(**kwargs) - if not os.path.exists(device): - raise ValueError("device file %s does not exist -- abort" % device) - self._sl = serial.Serial( - port = device, - parity = serial.PARITY_EVEN, - bytesize = serial.EIGHTBITS, - stopbits = serial.STOPBITS_TWO, - timeout = 1, - xonxoff = 0, - rtscts = 0, - baudrate = baudrate, - ) - self._rst_pin = rst - self._debug = debug - self._atr = None + def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 9600, rst: str = '-rts', + debug: bool = False, **kwargs): + super().__init__(**kwargs) + if not os.path.exists(device): + raise ValueError("device file %s does not exist -- abort" % device) + self._sl = serial.Serial( + port=device, + parity=serial.PARITY_EVEN, + bytesize=serial.EIGHTBITS, + stopbits=serial.STOPBITS_TWO, + timeout=1, + xonxoff=0, + rtscts=0, + baudrate=baudrate, + ) + self._rst_pin = rst + self._debug = debug + self._atr = None - def __del__(self): - if (hasattr(self, "_sl")): - self._sl.close() + def __del__(self): + if (hasattr(self, "_sl")): + self._sl.close() - def wait_for_card(self, timeout=None, newcardonly=False): - # Direct try - existing = False + def wait_for_card(self, timeout=None, newcardonly=False): + # Direct try + existing = False - try: - self.reset_card() - if not newcardonly: - return - else: - existing = True - except NoCardError: - pass + try: + self.reset_card() + if not newcardonly: + return + else: + existing = True + except NoCardError: + pass - # Poll ... - mt = time.time() + timeout if timeout is not None else None - pe = 0 + # Poll ... + mt = time.time() + timeout if timeout is not None else None + pe = 0 - while (mt is None) or (time.time() < mt): - try: - time.sleep(0.5) - self.reset_card() - if not existing: - return - except NoCardError: - existing = False - except ProtocolError: - if existing: - existing = False - else: - # Tolerate a couple of protocol error ... can happen if - # we try when the card is 'half' inserted - pe += 1 - if (pe > 2): - raise + while (mt is None) or (time.time() < mt): + try: + time.sleep(0.5) + self.reset_card() + if not existing: + return + except NoCardError: + existing = False + except ProtocolError: + if existing: + existing = False + else: + # Tolerate a couple of protocol error ... can happen if + # we try when the card is 'half' inserted + pe += 1 + if (pe > 2): + raise - # Timed out ... - raise NoCardError() + # Timed out ... + raise NoCardError() - def connect(self): - self.reset_card() + def connect(self): + self.reset_card() - def get_atr(self): - return self._atr + def get_atr(self): + return self._atr - def disconnect(self): - pass # Nothing to do really ... + def disconnect(self): + pass # Nothing to do really ... - def reset_card(self): - rv = self._reset_card() - if rv == 0: - raise NoCardError() - elif rv < 0: - raise ProtocolError() + def reset_card(self): + rv = self._reset_card() + if rv == 0: + raise NoCardError() + elif rv < 0: + raise ProtocolError() - def _reset_card(self): - self._atr = None - rst_meth_map = { - 'rts': self._sl.setRTS, - 'dtr': self._sl.setDTR, - } - rst_val_map = { '+':0, '-':1 } + def _reset_card(self): + self._atr = None + rst_meth_map = { + 'rts': self._sl.setRTS, + 'dtr': self._sl.setDTR, + } + rst_val_map = {'+': 0, '-': 1} - try: - rst_meth = rst_meth_map[self._rst_pin[1:]] - rst_val = rst_val_map[self._rst_pin[0]] - except: - raise ValueError('Invalid reset pin %s' % self._rst_pin) + try: + rst_meth = rst_meth_map[self._rst_pin[1:]] + rst_val = rst_val_map[self._rst_pin[0]] + except: + raise ValueError('Invalid reset pin %s' % self._rst_pin) - rst_meth(rst_val) - time.sleep(0.1) # 100 ms - self._sl.flushInput() - rst_meth(rst_val ^ 1) + rst_meth(rst_val) + time.sleep(0.1) # 100 ms + self._sl.flushInput() + rst_meth(rst_val ^ 1) - b = self._rx_byte() - if not b: - return 0 - if ord(b) != 0x3b: - return -1 - self._dbg_print("TS: 0x%x Direct convention" % ord(b)) + b = self._rx_byte() + if not b: + return 0 + if ord(b) != 0x3b: + return -1 + self._dbg_print("TS: 0x%x Direct convention" % ord(b)) - while ord(b) == 0x3b: - b = self._rx_byte() + while ord(b) == 0x3b: + b = self._rx_byte() - if not b: - return -1 - t0 = ord(b) - self._dbg_print("T0: 0x%x" % t0) - self._atr = [0x3b, ord(b)] + if not b: + return -1 + t0 = ord(b) + self._dbg_print("T0: 0x%x" % t0) + self._atr = [0x3b, ord(b)] - for i in range(4): - if t0 & (0x10 << i): - b = self._rx_byte() - self._atr.append(ord(b)) - self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b))) + for i in range(4): + if t0 & (0x10 << i): + b = self._rx_byte() + self._atr.append(ord(b)) + self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b))) - for i in range(0, t0 & 0xf): - b = self._rx_byte() - self._atr.append(ord(b)) - self._dbg_print("Historical = %x" % ord(b)) + for i in range(0, t0 & 0xf): + b = self._rx_byte() + self._atr.append(ord(b)) + self._dbg_print("Historical = %x" % ord(b)) - while True: - x = self._rx_byte() - if not x: - break - self._atr.append(ord(x)) - self._dbg_print("Extra: %x" % ord(x)) + while True: + x = self._rx_byte() + if not x: + break + self._atr.append(ord(x)) + self._dbg_print("Extra: %x" % ord(x)) - return 1 + return 1 - def _dbg_print(self, s): - if self._debug: - print(s) + def _dbg_print(self, s): + if self._debug: + print(s) - def _tx_byte(self, b): - self._sl.write(b) - r = self._sl.read() - if r != b: # TX and RX are tied, so we must clear the echo - raise ProtocolError("Bad echo value. Expected %02x, got %s)" % (ord(b), '%02x'%ord(r) if r else '(nil)')) + def _tx_byte(self, b): + self._sl.write(b) + r = self._sl.read() + if r != b: # TX and RX are tied, so we must clear the echo + raise ProtocolError("Bad echo value. Expected %02x, got %s)" % ( + ord(b), '%02x' % ord(r) if r else '(nil)')) - def _tx_string(self, s): - """This is only safe if it's guaranteed the card won't send any data - during the time of tx of the string !!!""" - self._sl.write(s) - r = self._sl.read(len(s)) - if r != s: # TX and RX are tied, so we must clear the echo - raise ProtocolError("Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r))) + def _tx_string(self, s): + """This is only safe if it's guaranteed the card won't send any data + during the time of tx of the string !!!""" + self._sl.write(s) + r = self._sl.read(len(s)) + if r != s: # TX and RX are tied, so we must clear the echo + raise ProtocolError( + "Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r))) - def _rx_byte(self): - return self._sl.read() + def _rx_byte(self): + return self._sl.read() - def _send_apdu_raw(self, pdu): + def _send_apdu_raw(self, pdu): - pdu = h2b(pdu) - data_len = pdu[4] # P3 + pdu = h2b(pdu) + data_len = pdu[4] # P3 - # Send first CLASS,INS,P1,P2,P3 - self._tx_string(pdu[0:5]) + # Send first CLASS,INS,P1,P2,P3 + self._tx_string(pdu[0:5]) - # Wait ack which can be - # - INS: Command acked -> go ahead - # - 0x60: NULL, just wait some more - # - SW1: The card can apparently proceed ... - while True: - b = self._rx_byte() - if ord(b) == pdu[1]: - break - elif b != '\x60': - # Ok, it 'could' be SW1 - sw1 = b - sw2 = self._rx_byte() - nil = self._rx_byte() - if (sw2 and not nil): - return '', b2h(sw1+sw2) + # Wait ack which can be + # - INS: Command acked -> go ahead + # - 0x60: NULL, just wait some more + # - SW1: The card can apparently proceed ... + while True: + b = self._rx_byte() + if ord(b) == pdu[1]: + break + elif b != '\x60': + # Ok, it 'could' be SW1 + sw1 = b + sw2 = self._rx_byte() + nil = self._rx_byte() + if (sw2 and not nil): + return '', b2h(sw1+sw2) - raise ProtocolError() + raise ProtocolError() - # Send data (if any) - if len(pdu) > 5: - self._tx_string(pdu[5:]) + # Send data (if any) + if len(pdu) > 5: + self._tx_string(pdu[5:]) - # Receive data (including SW !) - # length = [P3 - tx_data (=len(pdu)-len(hdr)) + 2 (SW1//2) ] - to_recv = data_len - len(pdu) + 5 + 2 + # Receive data (including SW !) + # length = [P3 - tx_data (=len(pdu)-len(hdr)) + 2 (SW1//2) ] + to_recv = data_len - len(pdu) + 5 + 2 - data = bytes(0) - while (len(data) < to_recv): - b = self._rx_byte() - if (to_recv == 2) and (b == '\x60'): # Ignore NIL if we have no RX data (hack ?) - continue - if not b: - break - data += b + data = bytes(0) + while (len(data) < to_recv): + b = self._rx_byte() + if (to_recv == 2) and (b == '\x60'): # Ignore NIL if we have no RX data (hack ?) + continue + if not b: + break + data += b - # Split datafield from SW - if len(data) < 2: - return None, None - sw = data[-2:] - data = data[0:-2] + # Split datafield from SW + if len(data) < 2: + return None, None + sw = data[-2:] + data = data[0:-2] - # Return value - return b2h(data), b2h(sw) + # Return value + return b2h(data), b2h(sw) diff --git a/pySim/ts_102_221.py b/pySim/ts_102_221.py index 19554840..da4eb93b 100644 --- a/pySim/ts_102_221.py +++ b/pySim/ts_102_221.py @@ -76,7 +76,7 @@ ts_102_22x_cmdset = CardCommandSet('TS 102 22x', [ CardCommand('TERMINATE EF', 0xE8, ['0X', '4X']), CardCommand('TERMINATE CARD USAGE', 0xFE, ['0X', '4X']), CardCommand('RESIZE FILE', 0xD4, ['8X', 'CX']), - ]) +]) FCP_TLV_MAP = { @@ -92,7 +92,7 @@ FCP_TLV_MAP = { '80': 'file_size', '81': 'total_file_size', '88': 'short_file_id', - } +} # ETSI TS 102 221 11.1.1.4.6 FCP_Proprietary_TLV_MAP = { @@ -107,9 +107,11 @@ FCP_Proprietary_TLV_MAP = { '88': 'specific_uicc_env_cond', '89': 'p2p_cat_secured_apdu', # 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 + + def interpret_file_descriptor(in_hex): in_bin = h2b(in_hex) out = {} @@ -123,7 +125,7 @@ def interpret_file_descriptor(in_hex): 1: 'transparent', 2: 'linear_fixed', 6: 'cyclic', - 0x39: 'ber_tlv', + 0x39: 'ber_tlv', } fdb = in_bin[0] ftype = (fdb >> 3) & 7 @@ -140,6 +142,8 @@ def interpret_file_descriptor(in_hex): return out # ETSI TS 102 221 11.1.1.4.9 + + def interpret_life_cycle_sts_int(in_hex): lcsi = int(in_hex, 16) if lcsi == 0x00: @@ -157,35 +161,40 @@ def interpret_life_cycle_sts_int(in_hex): else: return in_hex + # ETSI TS 102 221 11.1.1.4.10 FCP_Pin_Status_TLV_MAP = { '90': 'ps_do', '95': 'usage_qualifier', '83': 'key_reference', - } +} + def interpret_ps_templ_do(in_hex): # cannot use the 'TLV' parser due to repeating tags #psdo_tlv = TLV(FCP_Pin_Status_TLV_MAP) - #return psdo_tlv.parse(in_hex) + # return psdo_tlv.parse(in_hex) return in_hex + # 'interpreter' functions for each tag FCP_interpreter_map = { '80': lambda x: int(x, 16), '82': interpret_file_descriptor, '8A': interpret_life_cycle_sts_int, 'C6': interpret_ps_templ_do, - } +} FCP_prorietary_interpreter_map = { '83': lambda x: int(x, 16), - } +} # 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 # just the generic ValueError, so we cannot ignore those either. Instead, # we insert a dict entry for every possible proprietary tag permitted + + def fixup_fcp_proprietary_tlv_map(tlv_map): if 'D0' in tlv_map: return @@ -193,7 +202,7 @@ def fixup_fcp_proprietary_tlv_map(tlv_map): i_hex = i2h([i]).upper() tlv_map[i_hex] = 'proprietary_' + i_hex # Other non-standard TLV objects found on some cards - tlv_map['9B'] = 'target_ef' # for sysmoUSIM-SJS1 + tlv_map['9B'] = 'target_ef' # for sysmoUSIM-SJS1 def tlv_key_replace(inmap, indata): @@ -204,6 +213,7 @@ def tlv_key_replace(inmap, indata): return key return {newkey(inmap, d[0]): d[1] for d in indata.items()} + def tlv_val_interpret(inmap, indata): def newval(inmap, key, val): if key in inmap: @@ -214,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 + class _AM_DO_DF(DataObject): def __init__(self): super().__init__('access_mode', 'Access Mode', tag=0x80) - def from_bytes(self, do:bytes): + def from_bytes(self, do: bytes): res = [] if len(do) != 1: 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): """ISO7816-4 9.3.2 Table 18 + 9.3.3.1 Table 31""" + def __init__(self): super().__init__('access_mode', 'Access Mode', tag=0x80) - def from_bytes(self, do:bytes): + def from_bytes(self, do: bytes): res = [] if len(do) != 1: raise ValueError("We only support single-byte AMF inside AM-DO") @@ -306,12 +318,14 @@ class _AM_DO_EF(DataObject): val |= 0x01 return val.to_bytes(1, 'big') + class _AM_DO_CHDR(DataObject): """Command Header Access Mode DO according to ISO 7816-4 Table 32.""" + def __init__(self, tag): super().__init__('command_header', 'Command Header Description', tag=tag) - def from_bytes(self, do:bytes): + def from_bytes(self, do: bytes): res = {} i = 0 if self.tag & 0x08: @@ -353,11 +367,12 @@ class _AM_DO_CHDR(DataObject): res.append(self.decoded['P2']) return res + AM_DO_CHDR = DataObjectChoice('am_do_chdr', members=[ - _AM_DO_CHDR(0x81), _AM_DO_CHDR(0x82), _AM_DO_CHDR(0x83), _AM_DO_CHDR(0x84), - _AM_DO_CHDR(0x85), _AM_DO_CHDR(0x86), _AM_DO_CHDR(0x87), _AM_DO_CHDR(0x88), - _AM_DO_CHDR(0x89), _AM_DO_CHDR(0x8a), _AM_DO_CHDR(0x8b), _AM_DO_CHDR(0x8c), - _AM_DO_CHDR(0x8d), _AM_DO_CHDR(0x8e), _AM_DO_CHDR(0x8f)]) + _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(0x89), _AM_DO_CHDR(0x8a), _AM_DO_CHDR(0x8b), _AM_DO_CHDR(0x8c), + _AM_DO_CHDR(0x8d), _AM_DO_CHDR(0x8e), _AM_DO_CHDR(0x8f)]) AM_DO_DF = AM_DO_CHDR | _AM_DO_DF() AM_DO_EF = AM_DO_CHDR | _AM_DO_EF() @@ -393,12 +408,15 @@ pin_names = bidict({ 0x8c: 'ADM8', 0x8d: 'ADM9', 0x8e: 'ADM10', - }) +}) + class CRT_DO(DataObject): """Control Reference Template as per TS 102 221 9.5.1""" + def __init__(self): - super().__init__('control_reference_template', 'Control Reference Template', tag=0xA4) + super().__init__('control_reference_template', + 'Control Reference Template', tag=0xA4) def from_bytes(self, do: bytes): """Decode a Control Reference Template DO.""" @@ -407,7 +425,8 @@ class CRT_DO(DataObject): if do[0] != 0x83 or do[1] != 0x01: raise ValueError('Unsupported Key Ref Tag or Len in CRT DO %s', do) if do[3:] != b'\x95\x01\x08': - raise ValueError('Unsupported Usage Qualifier Tag or Len in CRT DO %s', do) + raise ValueError( + 'Unsupported Usage Qualifier Tag or Len in CRT DO %s', do) self.encoded = do[0:6] self.decoded = pin_names[do[2]] return do[6:] @@ -417,11 +436,13 @@ class CRT_DO(DataObject): return b'\x83\x01' + pin.to_bytes(1, 'big') + b'\x95\x01\x08' # ISO7816-4 9.3.3 Table 33 + + class SecCondByte_DO(DataObject): def __init__(self, tag=0x9d): super().__init__('security_condition_byte', tag=tag) - def from_bytes(self, binary:bytes): + def from_bytes(self, binary: bytes): if len(binary) != 1: raise ValueError inb = binary[0] @@ -440,7 +461,7 @@ class SecCondByte_DO(DataObject): res.append('external_auth') if inb & 0x10: res.append('user_auth') - rd = {'mode': cond } + rd = {'mode': cond} if len(res): rd['conditions'] = res self.decoded = rd @@ -470,39 +491,47 @@ class SecCondByte_DO(DataObject): raise ValueError('Unknown condition %s' % c) return res.to_bytes(1, 'big') + Always_DO = TL0_DataObject('always', 'Always', 0x90) Never_DO = TL0_DataObject('never', 'Never', 0x97) + class Nested_DO(DataObject): """A DO that nests another DO/Choice/Sequence""" + def __init__(self, name, tag, choice): super().__init__(name, tag=tag) self.children = choice - def from_bytes(self, binary:bytes) -> list: + + def from_bytes(self, binary: bytes) -> list: remainder = binary self.decoded = [] while remainder: rc, remainder = self.children.decode(remainder) self.decoded.append(rc) return self.decoded + def to_bytes(self) -> bytes: encoded = [self.children.encode(d) for d in self.decoded] return b''.join(encoded) + OR_Template = DataObjectChoice('or_template', 'OR-Template', members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO()]) OR_DO = Nested_DO('or', 0xa0, OR_Template) AND_Template = DataObjectChoice('and_template', 'AND-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()]) AND_DO = Nested_DO('and', 0xa7, AND_Template) NOT_Template = DataObjectChoice('not_template', 'NOT-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()]) NOT_DO = Nested_DO('not', 0xaf, NOT_Template) SC_DO = DataObjectChoice('security_condition', 'Security Condition', members=[Always_DO, Never_DO, SecCondByte_DO(), SecCondByte_DO(0x9e), CRT_DO(), OR_DO, AND_DO, NOT_DO]) # TS 102 221 Section 13.1 + + class EF_DIR(LinFixedEF): class ApplicationLabel(BER_TLV_IE, tag=0x50): # TODO: UCS-2 coding option as per Annex A of TS 102 221 @@ -518,13 +547,15 @@ class EF_DIR(LinFixedEF): pass 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 # TS 102 221 Section 13.2 + + class EF_ICCID(TransparentEF): def __init__(self, fid='2fe2', sfid=0x02, name='EF.ICCID', desc='ICC Identification'): - super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10,10}) + super().__init__(fid, sfid=sfid, name=name, desc=desc, size={10, 10}) def _decode_hex(self, raw_hex): return {'iccid': dec_iccid(raw_hex)} @@ -533,14 +564,19 @@ class EF_ICCID(TransparentEF): return enc_iccid(abstract['iccid']) # TS 102 221 Section 13.3 + + class EF_PL(TransRecEF): def __init__(self, fid='2f05', sfid=0x05, name='EF.PL', desc='Preferred Languages'): - super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=2, size={2,None}) + super().__init__(fid, sfid=sfid, name=name, + desc=desc, rec_len=2, size={2, None}) + def _decode_record_bin(self, bin_data): if bin_data == b'\xff\xff': return None else: return bin_data.decode('ascii') + def _encode_record_bin(self, in_json): if in_json is None: return b'\xff\xff' @@ -556,7 +592,7 @@ class EF_ARR(LinFixedEF): self.shell_commands += [self.AddlShellCommands()] @staticmethod - def flatten(inp:list): + def flatten(inp: list): """Flatten the somewhat deep/complex/nested data returned from decoder.""" def sc_abbreviate(sc): if 'always' in sc: @@ -584,7 +620,7 @@ class EF_ARR(LinFixedEF): cla = None cmd = ts_102_22x_cmdset.lookup(ins, cla) if cmd: - name = cmd.name.lower().replace(' ','_') + name = cmd.name.lower().replace(' ', '_') by_mode[name] = sc_abbr else: raise ValueError @@ -594,7 +630,7 @@ class EF_ARR(LinFixedEF): def _decode_record_bin(self, raw_bin_data): # 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) # 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 :( @@ -628,15 +664,18 @@ class EF_ARR(LinFixedEF): # TS 102 221 Section 13.6 class EF_UMPC(TransparentEF): def __init__(self, fid='2f08', sfid=0x08, name='EF.UMPC', desc='UICC Maximum Power Consumption'): - super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5,5}) - addl_info = FlagsEnum(Byte, req_inc_idle_current=1, support_uicc_suspend=2) - self._construct = Struct('max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info) + super().__init__(fid, sfid=sfid, name=name, desc=desc, size={5, 5}) + addl_info = FlagsEnum(Byte, req_inc_idle_current=1, + support_uicc_suspend=2) + self._construct = Struct( + 'max_current_mA'/Int8ub, 't_op_s'/Int8ub, 'addl_info'/addl_info) + class CardProfileUICC(CardProfile): ORDER = 1 - def __init__(self, name = 'UICC'): + def __init__(self, name='UICC'): files = [ EF_DIR(), EF_ICCID(), @@ -646,78 +685,79 @@ class CardProfileUICC(CardProfile): EF_UMPC(), ] sw = { - 'Normal': { - '9000': 'Normal ending of the command', - '91xx': 'Normal ending of the command, with extra information from the proactive UICC containing a command for the terminal', - '92xx': 'Normal ending of the command, with extra information concerning an ongoing data transfer session', + 'Normal': { + '9000': 'Normal ending of the command', + '91xx': 'Normal ending of the command, with extra information from the proactive UICC containing a command for the terminal', + '92xx': 'Normal ending of the command, with extra information concerning an ongoing data transfer session', }, - 'Postponed processing': { - '9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed', + 'Postponed processing': { + '9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed', }, - 'Warnings': { - '6200': 'No information given, state of non-volatile memory unchanged', - '6281': 'Part of returned data may be corrupted', - '6282': 'End of file/record reached before reading Le bytes or unsuccessful search', - '6283': 'Selected file invalidated', - '6284': 'Selected file in termination state', - '62f1': 'More data available', - '62f2': 'More data available and proactive command pending', - '62f3': 'Response data available', - '63f1': 'More data expected', - '63f2': 'More data expected and proactive command pending', - '63cx': 'Command successful but after using an internal update retry routine X times', + 'Warnings': { + '6200': 'No information given, state of non-volatile memory unchanged', + '6281': 'Part of returned data may be corrupted', + '6282': 'End of file/record reached before reading Le bytes or unsuccessful search', + '6283': 'Selected file invalidated', + '6284': 'Selected file in termination state', + '62f1': 'More data available', + '62f2': 'More data available and proactive command pending', + '62f3': 'Response data available', + '63f1': 'More data expected', + '63f2': 'More data expected and proactive command pending', + '63cx': 'Command successful but after using an internal update retry routine X times', }, - 'Execution errors': { - '6400': 'No information given, state of non-volatile memory unchanged', - '6500': 'No information given, state of non-volatile memory changed', - '6581': 'Memory problem', + 'Execution errors': { + '6400': 'No information given, state of non-volatile memory unchanged', + '6500': 'No information given, state of non-volatile memory changed', + '6581': 'Memory problem', }, - 'Checking errors': { - '6700': 'Wrong length', - '67xx': 'The interpretation of this status word is command dependent', - '6b00': 'Wrong parameter(s) P1-P2', - '6d00': 'Instruction code not supported or invalid', - '6e00': 'Class not supported', - '6f00': 'Technical problem, no precise diagnosis', - '6fxx': 'The interpretation of this status word is command dependent', + 'Checking errors': { + '6700': 'Wrong length', + '67xx': 'The interpretation of this status word is command dependent', + '6b00': 'Wrong parameter(s) P1-P2', + '6d00': 'Instruction code not supported or invalid', + '6e00': 'Class not supported', + '6f00': 'Technical problem, no precise diagnosis', + '6fxx': 'The interpretation of this status word is command dependent', }, - 'Functions in CLA not supported': { - '6800': 'No information given', - '6881': 'Logical channel not supported', - '6882': 'Secure messaging not supported', + 'Functions in CLA not supported': { + '6800': 'No information given', + '6881': 'Logical channel not supported', + '6882': 'Secure messaging not supported', }, - 'Command not allowed': { - '6900': 'No information given', - '6981': 'Command incompatible with file structure', - '6982': 'Security status not satisfied', - '6983': 'Authentication/PIN method blocked', - '6984': 'Referenced data invalidated', - '6985': 'Conditions of use not satisfied', - '6986': 'Command not allowed (no EF selected)', - '6989': 'Command not allowed - secure channel - security not satisfied', + 'Command not allowed': { + '6900': 'No information given', + '6981': 'Command incompatible with file structure', + '6982': 'Security status not satisfied', + '6983': 'Authentication/PIN method blocked', + '6984': 'Referenced data invalidated', + '6985': 'Conditions of use not satisfied', + '6986': 'Command not allowed (no EF selected)', + '6989': 'Command not allowed - secure channel - security not satisfied', }, - 'Wrong parameters': { - '6a80': 'Incorrect parameters in the data field', - '6a81': 'Function not supported', - '6a82': 'File not found', - '6a83': 'Record not found', - '6a84': 'Not enough memory space', - '6a86': 'Incorrect parameters P1 to P2', - '6a87': 'Lc inconsistent with P1 to P2', - '6a88': 'Referenced data not found', + 'Wrong parameters': { + '6a80': 'Incorrect parameters in the data field', + '6a81': 'Function not supported', + '6a82': 'File not found', + '6a83': 'Record not found', + '6a84': 'Not enough memory space', + '6a86': 'Incorrect parameters P1 to P2', + '6a87': 'Lc inconsistent with P1 to P2', + '6a88': 'Referenced data not found', }, - 'Application errors': { - '9850': 'INCREASE cannot be performed, max value reached', - '9862': 'Authentication error, application specific', - '9863': 'Security session or association expired', - '9864': 'Minimum UICC suspension time is too long', + 'Application errors': { + '9850': 'INCREASE cannot be performed, max value reached', + '9862': 'Authentication error, application specific', + '9863': 'Security session or association expired', + '9864': 'Minimum UICC suspension time is too long', }, - } + } - super().__init__(name, desc='ETSI TS 102 221', cla="00", sel_ctrl="0004", files_in_mf=files, sw=sw) + super().__init__(name, desc='ETSI TS 102 221', cla="00", + sel_ctrl="0004", files_in_mf=files, sw=sw) @staticmethod - def decode_select_response(resp_hex:str) -> object: + def decode_select_response(resp_hex: str) -> object: """ETSI TS 102 221 Section 11.1.1.3""" fixup_fcp_proprietary_tlv_map(FCP_Proprietary_TLV_MAP) resp_hex = resp_hex.upper() @@ -738,9 +778,10 @@ class CardProfileUICC(CardProfile): return tlv_key_replace(FCP_TLV_MAP, r) @staticmethod - def match_with_card(scc:SimCardCommands) -> bool: + def match_with_card(scc: SimCardCommands) -> bool: return match_uicc(scc) + class CardProfileUICCSIM(CardProfileUICC): """Same as above, but including 2G SIM support""" @@ -754,5 +795,5 @@ class CardProfileUICCSIM(CardProfileUICC): self.files_in_mf.append(DF_GSM()) @staticmethod - def match_with_card(scc:SimCardCommands) -> bool: + def match_with_card(scc: SimCardCommands) -> bool: return match_uicc(scc) and match_sim(scc) diff --git a/pySim/ts_31_102.py b/pySim/ts_31_102.py index 3ae88d39..9e6ea78a 100644 --- a/pySim/ts_31_102.py +++ b/pySim/ts_31_102.py @@ -27,142 +27,157 @@ Various constants from 3GPP TS 31.102 V16.6.0 # # Mapping between USIM Service Number and its description +import pySim.ts_102_221 +from pySim.ts_51_011 import EF_ACMmax, EF_AAeM, EF_eMLPP, EF_CMI, EF_PNN +from pySim.ts_51_011 import EF_MMSN, EF_MMSICP, EF_MMSUP, EF_MMSUCP, EF_VGCS, EF_VGCSS, EF_NIA +from pySim.ts_51_011 import EF_SMSR, EF_DCK, EF_EXT, EF_CNL, EF_OPL, EF_MBI, EF_MWIS +from pySim.ts_51_011 import EF_CBMID, EF_CBMIR, EF_ADN, EF_SMS, EF_MSISDN, EF_SMSP, EF_SMSS +from pySim.ts_51_011 import EF_IMSI, EF_xPLMNwAcT, EF_SPN, EF_CBMI, EF_ACC, EF_PLMNsel +from pySim.ts_102_221 import EF_ARR +from pySim.tlv import * +from pySim.filesystem import * +from pySim.construct import * +from construct import Optional as COptional +from construct import * +from typing import Tuple +from struct import unpack, pack +import enum EF_UST_map = { - 1: 'Local Phone Book', - 2: 'Fixed Dialling Numbers (FDN)', - 3: 'Extension 2', - 4: 'Service Dialling Numbers (SDN)', - 5: 'Extension3', - 6: 'Barred Dialling Numbers (BDN)', - 7: 'Extension4', - 8: 'Outgoing Call Information (OCI and OCT)', - 9: 'Incoming Call Information (ICI and ICT)', - 10: 'Short Message Storage (SMS)', - 11: 'Short Message Status Reports (SMSR)', - 12: 'Short Message Service Parameters (SMSP)', - 13: 'Advice of Charge (AoC)', - 14: 'Capability Configuration Parameters 2 (CCP2)', - 15: 'Cell Broadcast Message Identifier', - 16: 'Cell Broadcast Message Identifier Ranges', - 17: 'Group Identifier Level 1', - 18: 'Group Identifier Level 2', - 19: 'Service Provider Name', - 20: 'User controlled PLMN selector with Access Technology', - 21: 'MSISDN', - 22: 'Image (IMG)', - 23: 'Support of Localised Service Areas (SoLSA)', - 24: 'Enhanced Multi-Level Precedence and Pre-emption Service', - 25: 'Automatic Answer for eMLPP', - 26: 'RFU', - 27: 'GSM Access', - 28: 'Data download via SMS-PP', - 29: 'Data download via SMS-CB', - 30: 'Call Control by USIM', - 31: 'MO-SMS Control by USIM', - 32: 'RUN AT COMMAND command', - 33: 'shall be set to 1', - 34: 'Enabled Services Table', - 35: 'APN Control List (ACL)', - 36: 'Depersonalisation Control Keys', - 37: 'Co-operative Network List', - 38: 'GSM security context', - 39: 'CPBCCH Information', - 40: 'Investigation Scan', - 41: 'MexE', - 42: 'Operator controlled PLMN selector with Access Technology', - 43: 'HPLMN selector with Access Technology', - 44: 'Extension 5', - 45: 'PLMN Network Name', - 46: 'Operator PLMN List', - 47: 'Mailbox Dialling Numbers', - 48: 'Message Waiting Indication Status', - 49: 'Call Forwarding Indication Status', - 50: 'Reserved and shall be ignored', - 51: 'Service Provider Display Information', - 52: 'Multimedia Messaging Service (MMS)', - 53: 'Extension 8', - 54: 'Call control on GPRS by USIM', - 55: 'MMS User Connectivity Parameters', - 56: 'Network\'s indication of alerting in the MS (NIA)', - 57: 'VGCS Group Identifier List (EFVGCS and EFVGCSS)', - 58: 'VBS Group Identifier List (EFVBS and EFVBSS)', - 59: 'Pseudonym', - 60: 'User Controlled PLMN selector for I-WLAN access', - 61: 'Operator Controlled PLMN selector for I-WLAN access', - 62: 'User controlled WSID list', - 63: 'Operator controlled WSID list', - 64: 'VGCS security', - 65: 'VBS security', - 66: 'WLAN Reauthentication Identity', - 67: 'Multimedia Messages Storage', - 68: 'Generic Bootstrapping Architecture (GBA)', - 69: 'MBMS security', - 70: 'Data download via USSD and USSD application mode', - 71: 'Equivalent HPLMN', - 72: 'Additional TERMINAL PROFILE after UICC activation', - 73: 'Equivalent HPLMN Presentation Indication', - 74: 'Last RPLMN Selection Indication', - 75: 'OMA BCAST Smart Card Profile', - 76: 'GBA-based Local Key Establishment Mechanism', - 77: 'Terminal Applications', - 78: 'Service Provider Name Icon', - 79: 'PLMN Network Name Icon', - 80: 'Connectivity Parameters for USIM IP connections', - 81: 'Home I-WLAN Specific Identifier List', - 82: 'I-WLAN Equivalent HPLMN Presentation Indication', - 83: 'I-WLAN HPLMN Priority Indication', - 84: 'I-WLAN Last Registered PLMN', - 85: 'EPS Mobility Management Information', - 86: 'Allowed CSG Lists and corresponding indications', - 87: 'Call control on EPS PDN connection by USIM', - 88: 'HPLMN Direct Access', - 89: 'eCall Data', - 90: 'Operator CSG Lists and corresponding indications', - 91: 'Support for SM-over-IP', - 92: 'Support of CSG Display Control', - 93: 'Communication Control for IMS by USIM', - 94: 'Extended Terminal Applications', - 95: 'Support of UICC access to IMS', - 96: 'Non-Access Stratum configuration by USIM', - 97: 'PWS configuration by USIM', - 98: 'RFU', - 99: 'URI support by UICC', - 100: 'Extended EARFCN support', - 101: 'ProSe', - 102: 'USAT Application Pairing', - 103: 'Media Type support', - 104: 'IMS call disconnection cause', - 105: 'URI support for MO SHORT MESSAGE CONTROL', - 106: 'ePDG configuration Information support', - 107: 'ePDG configuration Information configured', - 108: 'ACDC support', - 109: 'MCPTT', - 110: 'ePDG configuration Information for Emergency Service support', - 111: 'ePDG configuration Information for Emergency Service configured', - 112: 'eCall Data over IMS', - 113: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111 [12]', - 114: 'From Preferred', - 115: 'IMS configuration data', - 116: 'TV configuration', - 117: '3GPP PS Data Off', - 118: '3GPP PS Data Off Service List', - 119: 'V2X', - 120: 'XCAP Configuration Data', - 121: 'EARFCN list for MTC/NB-IOT UEs', - 122: '5GS Mobility Management Information', - 123: '5G Security Parameters', - 124: 'Subscription identifier privacy support', - 125: 'SUCI calculation by the USIM', - 126: 'UAC Access Identities support', - 127: 'Expect control plane-based Steering of Roaming information during initial registration in VPLMN', - 128: 'Call control on PDU Session by USIM', - 129: '5GS Operator PLMN List', - 130: 'Support for SUPI of type NSI or GLI or GCI', - 131: '3GPP PS Data Off separate Home and Roaming lists', - 132: 'Support for URSP by USIM', - 133: '5G Security Parameters extended', - 134: 'MuD and MiD configuration data', - 135: 'Support for Trusted non-3GPP access networks by USIM' + 1: 'Local Phone Book', + 2: 'Fixed Dialling Numbers (FDN)', + 3: 'Extension 2', + 4: 'Service Dialling Numbers (SDN)', + 5: 'Extension3', + 6: 'Barred Dialling Numbers (BDN)', + 7: 'Extension4', + 8: 'Outgoing Call Information (OCI and OCT)', + 9: 'Incoming Call Information (ICI and ICT)', + 10: 'Short Message Storage (SMS)', + 11: 'Short Message Status Reports (SMSR)', + 12: 'Short Message Service Parameters (SMSP)', + 13: 'Advice of Charge (AoC)', + 14: 'Capability Configuration Parameters 2 (CCP2)', + 15: 'Cell Broadcast Message Identifier', + 16: 'Cell Broadcast Message Identifier Ranges', + 17: 'Group Identifier Level 1', + 18: 'Group Identifier Level 2', + 19: 'Service Provider Name', + 20: 'User controlled PLMN selector with Access Technology', + 21: 'MSISDN', + 22: 'Image (IMG)', + 23: 'Support of Localised Service Areas (SoLSA)', + 24: 'Enhanced Multi-Level Precedence and Pre-emption Service', + 25: 'Automatic Answer for eMLPP', + 26: 'RFU', + 27: 'GSM Access', + 28: 'Data download via SMS-PP', + 29: 'Data download via SMS-CB', + 30: 'Call Control by USIM', + 31: 'MO-SMS Control by USIM', + 32: 'RUN AT COMMAND command', + 33: 'shall be set to 1', + 34: 'Enabled Services Table', + 35: 'APN Control List (ACL)', + 36: 'Depersonalisation Control Keys', + 37: 'Co-operative Network List', + 38: 'GSM security context', + 39: 'CPBCCH Information', + 40: 'Investigation Scan', + 41: 'MexE', + 42: 'Operator controlled PLMN selector with Access Technology', + 43: 'HPLMN selector with Access Technology', + 44: 'Extension 5', + 45: 'PLMN Network Name', + 46: 'Operator PLMN List', + 47: 'Mailbox Dialling Numbers', + 48: 'Message Waiting Indication Status', + 49: 'Call Forwarding Indication Status', + 50: 'Reserved and shall be ignored', + 51: 'Service Provider Display Information', + 52: 'Multimedia Messaging Service (MMS)', + 53: 'Extension 8', + 54: 'Call control on GPRS by USIM', + 55: 'MMS User Connectivity Parameters', + 56: 'Network\'s indication of alerting in the MS (NIA)', + 57: 'VGCS Group Identifier List (EFVGCS and EFVGCSS)', + 58: 'VBS Group Identifier List (EFVBS and EFVBSS)', + 59: 'Pseudonym', + 60: 'User Controlled PLMN selector for I-WLAN access', + 61: 'Operator Controlled PLMN selector for I-WLAN access', + 62: 'User controlled WSID list', + 63: 'Operator controlled WSID list', + 64: 'VGCS security', + 65: 'VBS security', + 66: 'WLAN Reauthentication Identity', + 67: 'Multimedia Messages Storage', + 68: 'Generic Bootstrapping Architecture (GBA)', + 69: 'MBMS security', + 70: 'Data download via USSD and USSD application mode', + 71: 'Equivalent HPLMN', + 72: 'Additional TERMINAL PROFILE after UICC activation', + 73: 'Equivalent HPLMN Presentation Indication', + 74: 'Last RPLMN Selection Indication', + 75: 'OMA BCAST Smart Card Profile', + 76: 'GBA-based Local Key Establishment Mechanism', + 77: 'Terminal Applications', + 78: 'Service Provider Name Icon', + 79: 'PLMN Network Name Icon', + 80: 'Connectivity Parameters for USIM IP connections', + 81: 'Home I-WLAN Specific Identifier List', + 82: 'I-WLAN Equivalent HPLMN Presentation Indication', + 83: 'I-WLAN HPLMN Priority Indication', + 84: 'I-WLAN Last Registered PLMN', + 85: 'EPS Mobility Management Information', + 86: 'Allowed CSG Lists and corresponding indications', + 87: 'Call control on EPS PDN connection by USIM', + 88: 'HPLMN Direct Access', + 89: 'eCall Data', + 90: 'Operator CSG Lists and corresponding indications', + 91: 'Support for SM-over-IP', + 92: 'Support of CSG Display Control', + 93: 'Communication Control for IMS by USIM', + 94: 'Extended Terminal Applications', + 95: 'Support of UICC access to IMS', + 96: 'Non-Access Stratum configuration by USIM', + 97: 'PWS configuration by USIM', + 98: 'RFU', + 99: 'URI support by UICC', + 100: 'Extended EARFCN support', + 101: 'ProSe', + 102: 'USAT Application Pairing', + 103: 'Media Type support', + 104: 'IMS call disconnection cause', + 105: 'URI support for MO SHORT MESSAGE CONTROL', + 106: 'ePDG configuration Information support', + 107: 'ePDG configuration Information configured', + 108: 'ACDC support', + 109: 'MCPTT', + 110: 'ePDG configuration Information for Emergency Service support', + 111: 'ePDG configuration Information for Emergency Service configured', + 112: 'eCall Data over IMS', + 113: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111 [12]', + 114: 'From Preferred', + 115: 'IMS configuration data', + 116: 'TV configuration', + 117: '3GPP PS Data Off', + 118: '3GPP PS Data Off Service List', + 119: 'V2X', + 120: 'XCAP Configuration Data', + 121: 'EARFCN list for MTC/NB-IOT UEs', + 122: '5GS Mobility Management Information', + 123: '5G Security Parameters', + 124: 'Subscription identifier privacy support', + 125: 'SUCI calculation by the USIM', + 126: 'UAC Access Identities support', + 127: 'Expect control plane-based Steering of Roaming information during initial registration in VPLMN', + 128: 'Call control on PDU Session by USIM', + 129: '5GS Operator PLMN List', + 130: 'Support for SUPI of type NSI or GLI or GCI', + 131: '3GPP PS Data Off separate Home and Roaming lists', + 132: 'Support for URSP by USIM', + 133: '5G Security Parameters extended', + 134: 'MuD and MiD configuration data', + 135: 'Support for Trusted non-3GPP access networks by USIM' } # Mapping between USIM Enbled Service Number and its description @@ -173,138 +188,123 @@ EF_EST_map = { } LOCI_STATUS_map = { - 0: 'updated', - 1: 'not updated', - 2: 'plmn not allowed', - 3: 'locatation area not allowed' + 0: 'updated', + 1: 'not updated', + 2: 'plmn not allowed', + 3: 'locatation area not allowed' } EF_USIM_ADF_map = { - 'LI': '6F05', - 'ARR': '6F06', - 'IMSI': '6F07', - 'Keys': '6F08', - 'KeysPS': '6F09', - 'DCK': '6F2C', - 'HPPLMN': '6F31', - 'CNL': '6F32', - 'ACMmax': '6F37', - 'UST': '6F38', - 'ACM': '6F39', - 'FDN': '6F3B', - 'SMS': '6F3C', - 'GID1': '6F3E', - 'GID2': '6F3F', - 'MSISDN': '6F40', - 'PUCT': '6F41', - 'SMSP': '6F42', - 'SMSS': '6F42', - 'CBMI': '6F45', - 'SPN': '6F46', - 'SMSR': '6F47', - 'CBMID': '6F48', - 'SDN': '6F49', - 'EXT2': '6F4B', - 'EXT3': '6F4C', - 'BDN': '6F4D', - 'EXT5': '6F4E', - 'CCP2': '6F4F', - 'CBMIR': '6F50', - 'EXT4': '6F55', - 'EST': '6F56', - 'ACL': '6F57', - 'CMI': '6F58', - 'START-HFN': '6F5B', - 'THRESHOLD': '6F5C', - 'PLMNwAcT': '6F60', - 'OPLMNwAcT': '6F61', - 'HPLMNwAcT': '6F62', - 'PSLOCI': '6F73', - 'ACC': '6F78', - 'FPLMN': '6F7B', - 'LOCI': '6F7E', - 'ICI': '6F80', - 'OCI': '6F81', - 'ICT': '6F82', - 'OCT': '6F83', - 'AD': '6FAD', - 'VGCS': '6FB1', - 'VGCSS': '6FB2', - 'VBS': '6FB3', - 'VBSS': '6FB4', - 'eMLPP': '6FB5', - 'AAeM': '6FB6', - 'ECC': '6FB7', - 'Hiddenkey': '6FC3', - 'NETPAR': '6FC4', - 'PNN': '6FC5', - 'OPL': '6FC6', - 'MBDN': '6FC7', - 'EXT6': '6FC8', - 'MBI': '6FC9', - 'MWIS': '6FCA', - 'CFIS': '6FCB', - 'EXT7': '6FCC', - 'SPDI': '6FCD', - 'MMSN': '6FCE', - 'EXT8': '6FCF', - 'MMSICP': '6FD0', - 'MMSUP': '6FD1', - 'MMSUCP': '6FD2', - 'NIA': '6FD3', - 'VGCSCA': '6FD4', - 'VBSCA': '6FD5', - 'GBAP': '6FD6', - 'MSK': '6FD7', - 'MUK': '6FD8', - 'EHPLMN': '6FD9', - 'GBANL': '6FDA', - 'EHPLMNPI': '6FDB', - 'LRPLMNSI': '6FDC', - 'NAFKCA': '6FDD', - 'SPNI': '6FDE', - 'PNNI': '6FDF', - 'NCP-IP': '6FE2', - 'EPSLOCI': '6FE3', - 'EPSNSC': '6FE4', - 'UFC': '6FE6', - 'UICCIARI': '6FE7', - 'NASCONFIG': '6FE8', - 'PWC': '6FEC', - 'FDNURI': '6FED', - 'BDNURI': '6FEE', - 'SDNURI': '6FEF', - 'IWL': '6FF0', - 'IPS': '6FF1', - 'IPD': '6FF2', - 'ePDGId': '6FF3', - 'ePDGSelection': '6FF4', - 'ePDGIdEm': '6FF5', - 'ePDGSelectionEm': '6FF6', + 'LI': '6F05', + 'ARR': '6F06', + 'IMSI': '6F07', + 'Keys': '6F08', + 'KeysPS': '6F09', + 'DCK': '6F2C', + 'HPPLMN': '6F31', + 'CNL': '6F32', + 'ACMmax': '6F37', + 'UST': '6F38', + 'ACM': '6F39', + 'FDN': '6F3B', + 'SMS': '6F3C', + 'GID1': '6F3E', + 'GID2': '6F3F', + 'MSISDN': '6F40', + 'PUCT': '6F41', + 'SMSP': '6F42', + 'SMSS': '6F42', + 'CBMI': '6F45', + 'SPN': '6F46', + 'SMSR': '6F47', + 'CBMID': '6F48', + 'SDN': '6F49', + 'EXT2': '6F4B', + 'EXT3': '6F4C', + 'BDN': '6F4D', + 'EXT5': '6F4E', + 'CCP2': '6F4F', + 'CBMIR': '6F50', + 'EXT4': '6F55', + 'EST': '6F56', + 'ACL': '6F57', + 'CMI': '6F58', + 'START-HFN': '6F5B', + 'THRESHOLD': '6F5C', + 'PLMNwAcT': '6F60', + 'OPLMNwAcT': '6F61', + 'HPLMNwAcT': '6F62', + 'PSLOCI': '6F73', + 'ACC': '6F78', + 'FPLMN': '6F7B', + 'LOCI': '6F7E', + 'ICI': '6F80', + 'OCI': '6F81', + 'ICT': '6F82', + 'OCT': '6F83', + 'AD': '6FAD', + 'VGCS': '6FB1', + 'VGCSS': '6FB2', + 'VBS': '6FB3', + 'VBSS': '6FB4', + 'eMLPP': '6FB5', + 'AAeM': '6FB6', + 'ECC': '6FB7', + 'Hiddenkey': '6FC3', + 'NETPAR': '6FC4', + 'PNN': '6FC5', + 'OPL': '6FC6', + 'MBDN': '6FC7', + 'EXT6': '6FC8', + 'MBI': '6FC9', + 'MWIS': '6FCA', + 'CFIS': '6FCB', + 'EXT7': '6FCC', + 'SPDI': '6FCD', + 'MMSN': '6FCE', + 'EXT8': '6FCF', + 'MMSICP': '6FD0', + 'MMSUP': '6FD1', + 'MMSUCP': '6FD2', + 'NIA': '6FD3', + 'VGCSCA': '6FD4', + 'VBSCA': '6FD5', + 'GBAP': '6FD6', + 'MSK': '6FD7', + 'MUK': '6FD8', + 'EHPLMN': '6FD9', + 'GBANL': '6FDA', + 'EHPLMNPI': '6FDB', + 'LRPLMNSI': '6FDC', + 'NAFKCA': '6FDD', + 'SPNI': '6FDE', + 'PNNI': '6FDF', + 'NCP-IP': '6FE2', + 'EPSLOCI': '6FE3', + 'EPSNSC': '6FE4', + 'UFC': '6FE6', + 'UICCIARI': '6FE7', + 'NASCONFIG': '6FE8', + 'PWC': '6FEC', + 'FDNURI': '6FED', + 'BDNURI': '6FEE', + 'SDNURI': '6FEF', + 'IWL': '6FF0', + 'IPS': '6FF1', + 'IPD': '6FF2', + 'ePDGId': '6FF3', + 'ePDGSelection': '6FF4', + 'ePDGIdEm': '6FF5', + 'ePDGSelectionEm': '6FF6', } ###################################################################### # ADF.USIM ###################################################################### -import enum -from struct import unpack, pack -from typing import Tuple -from construct import * -from construct import Optional as COptional -from pySim.construct import * -from pySim.filesystem import * -from pySim.tlv import * -from pySim.ts_102_221 import EF_ARR -from pySim.ts_51_011 import EF_IMSI, EF_xPLMNwAcT, EF_SPN, EF_CBMI, EF_ACC, EF_PLMNsel -from pySim.ts_51_011 import EF_CBMID, EF_CBMIR, EF_ADN, EF_SMS, EF_MSISDN, EF_SMSP, EF_SMSS -from pySim.ts_51_011 import EF_SMSR, EF_DCK, EF_EXT, EF_CNL, EF_OPL, EF_MBI, EF_MWIS -from pySim.ts_51_011 import EF_MMSN, EF_MMSICP, EF_MMSUP, EF_MMSUCP, EF_VGCS, EF_VGCSS, EF_NIA -from pySim.ts_51_011 import EF_ACMmax, EF_AAeM, EF_eMLPP, EF_CMI, EF_PNN - -import pySim.ts_102_221 # 3GPP TS 31.102 Section 4.4.11.4 (EF_5GS3GPPNSC) + class EF_5GS3GPPNSC(LinFixedEF): class NgKSI(BER_TLV_IE, tag=0x80): _construct = Int8ub @@ -327,17 +327,19 @@ class EF_5GS3GPPNSC(LinFixedEF): _construct = BitStruct('ciphering'/Nibble, 'integrity'/Nibble) class FiveGSNasSecurityContext(BER_TLV_IE, tag=0xA0, - nested=[NgKSI, K_AMF, UplinkNASCount, - DownlinkNASCount, IdsOfSelectedNasAlgos, - IdsOfSelectedEpsAlgos]): + nested=[NgKSI, K_AMF, UplinkNASCount, + DownlinkNASCount, IdsOfSelectedNasAlgos, + IdsOfSelectedEpsAlgos]): pass def __init__(self, fid="4f03", sfid=0x03, name='EF.5GS3GPPNSC', rec_len={57, None}, - desc='5GS 3GPP Access NAS Security Context'): + desc='5GS 3GPP Access NAS Security Context'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._tlv = EF_5GS3GPPNSC.FiveGSNasSecurityContext # 3GPP TS 31.102 Section 4.4.11.6 + + class EF_5GAUTHKEYS(TransparentEF): class K_AUSF(BER_TLV_IE, tag=0x80): _construct = HexAdapter(GreedyBytes) @@ -349,42 +351,50 @@ class EF_5GAUTHKEYS(TransparentEF): pass def __init__(self, fid='4f05', sfid=0x05, name='EF.5GAUTHKEYS', size={68, None}, - desc='5G authentication keys'): + desc='5G authentication keys'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._tlv = EF_5GAUTHKEYS.FiveGAuthKeys # 3GPP TS 31.102 Section 4.4.11.8 + + class ProtSchemeIdList(BER_TLV_IE, tag=0xa0): # FIXME: 3GPP TS 24.501 Protection Scheme Identifier # repeated sequence of (id, index) tuples - _construct = GreedyRange(Struct('id'/Enum(Byte, null=0, A=1, B=2), 'index'/Int8ub)) + _construct = GreedyRange( + Struct('id'/Enum(Byte, null=0, A=1, B=2), 'index'/Int8ub)) + class HomeNetPubKeyId(BER_TLV_IE, tag=0x80): # 3GPP TS 24.501 / 3GPP TS 23.003 _construct = Int8ub + class HomeNetPubKey(BER_TLV_IE, tag=0x81): # FIXME: RFC 5480 _construct = HexAdapter(GreedyBytes) + class HomeNetPubKeyList(BER_TLV_IE, tag=0xa1, - nested=[HomeNetPubKeyId, HomeNetPubKey]): + nested=[HomeNetPubKeyId, HomeNetPubKey]): pass # 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 # TS 31.102 4.4.11.8 class EF_SUCI_Calc_Info(TransparentEF): def __init__(self, fid="4f07", sfid=0x07, name='EF.SUCI_Calc_Info', size={2, None}, - desc='SUCI Calc Info'): + desc='SUCI Calc Info'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) def _encode_prot_scheme_id_list(self, in_list): out_bytes = [0xa0] - out_bytes.append(len(in_list)*2) # two byte per entry + out_bytes.append(len(in_list)*2) # two byte per entry # position in list determines priority; high-priority items (low index) come first for scheme in sorted(in_list, key=lambda item: item["priority"]): @@ -394,15 +404,15 @@ class EF_SUCI_Calc_Info(TransparentEF): return out_bytes def _encode_hnet_pubkey_list(self, hnet_pubkey_list): - out_bytes = [0xa1] # pubkey list tag - out_bytes.append(0x00) # length filled later + out_bytes = [0xa1] # pubkey list tag + out_bytes.append(0x00) # length filled later length = 0 for key in hnet_pubkey_list: - out_bytes.append(0x80) # identifier tag - out_bytes.append(0x01) # TODO size, fixed to 1 byte + out_bytes.append(0x80) # identifier tag + out_bytes.append(0x01) # TODO size, fixed to 1 byte out_bytes.append(key["hnet_pubkey_identifier"]) - out_bytes.append(0x81) # key tag + out_bytes.append(0x81) # key tag out_bytes.append(len(key["hnet_pubkey"])//2) length += 5+len(key["hnet_pubkey"])//2 @@ -414,7 +424,8 @@ class EF_SUCI_Calc_Info(TransparentEF): return out_bytes def _encode_hex(self, in_json): - out_bytes = self._encode_prot_scheme_id_list(in_json['prot_scheme_id_list']) + out_bytes = self._encode_prot_scheme_id_list( + in_json['prot_scheme_id_list']) out_bytes += self._encode_hnet_pubkey_list(in_json['hnet_pubkey_list']) return "".join(["%02X" % i for i in out_bytes]) @@ -424,7 +435,7 @@ class EF_SUCI_Calc_Info(TransparentEF): # two bytes per entry while pos < len(in_bytes): prot_scheme = { - 'priority': pos//2, # first in list: high priority + 'priority': pos//2, # first in list: high priority 'identifier': in_bytes[pos], 'key_index': in_bytes[pos+1] } @@ -447,7 +458,8 @@ class EF_SUCI_Calc_Info(TransparentEF): print("missing Home Network Public Key Identifier tag") return {} pos += 1 - hnet_pubkey_id_len = in_bytes[pos] # TODO might be more than 1 byte? + # TODO might be more than 1 byte? + hnet_pubkey_id_len = in_bytes[pos] pos += 1 hnet_pubkey_id = in_bytes[pos:pos+hnet_pubkey_id_len][0] pos += hnet_pubkey_id_len @@ -479,10 +491,11 @@ class EF_SUCI_Calc_Info(TransparentEF): return {} pos += 1 - 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 # decode Protection Scheme Identifier List data object - prot_scheme_id_list = self._decode_prot_scheme_id_list(in_bytes[pos:pos+prot_scheme_id_list_len]) + prot_scheme_id_list = self._decode_prot_scheme_id_list( + in_bytes[pos:pos+prot_scheme_id_list_len]) pos += prot_scheme_id_list_len # remaining data holds Home Network Public Key Data Object @@ -496,16 +509,19 @@ class EF_SUCI_Calc_Info(TransparentEF): def _encode_bin(self, in_json): return h2b(self._encode_hex(in_json)) + class EF_LI(TransRecEF): - def __init__(self, fid='6f05', sfid=None, name='EF.LI', size={2,None}, rec_len=2, + def __init__(self, fid='6f05', sfid=None, name='EF.LI', size={2, None}, rec_len=2, desc='Language Indication'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) + def _decode_record_bin(self, in_bin): if in_bin == b'\xff\xff': return None else: # officially this is 7-bit GSM alphabet with one padding bit in each byte return in_bin.decode('ascii') + def _encode_record_bin(self, in_json): if in_json == None: return b'\xff\xff' @@ -513,63 +529,75 @@ class EF_LI(TransRecEF): # officially this is 7-bit GSM alphabet with one padding bit in each byte return in_json.encode('ascii') + class EF_Keys(TransparentEF): - def __init__(self, fid='6f08', sfid=0x08, name='EF.Keys', size={33,33}, + def __init__(self, fid='6f08', sfid=0x08, name='EF.Keys', size={33, 33}, desc='Ciphering and Integrity Keys'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) - self._construct = Struct('ksi'/Int8ub, 'ck'/HexAdapter(Bytes(16)), 'ik'/HexAdapter(Bytes(16))) + self._construct = Struct( + 'ksi'/Int8ub, 'ck'/HexAdapter(Bytes(16)), 'ik'/HexAdapter(Bytes(16))) # TS 31.102 Section 4.2.6 + + class EF_HPPLMN(TransparentEF): - def __init__(self, fid='6f31', sfid=0x12, name='EF.HPPLMN', size={1,1}, + def __init__(self, fid='6f31', sfid=0x12, name='EF.HPPLMN', size={1, 1}, desc='Higher Priority PLMN search period'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Int8ub # TS 31.102 Section 4.2.8 + + class EF_UServiceTable(TransparentEF): def __init__(self, fid, sfid, name, desc, size, table): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, size=size) self.table = table # add those commands to the general commands of a TransparentEF self.shell_commands += [self.AddlShellCommands()] + @staticmethod - def _bit_byte_offset_for_service(service:int) -> Tuple[int, int]: + def _bit_byte_offset_for_service(service: int) -> Tuple[int, int]: i = service - 1 byte_offset = i//8 bit_offset = (i % 8) return (byte_offset, bit_offset) + def _decode_bin(self, in_bin): ret = {} - for i in range (0, len(in_bin)): + for i in range(0, len(in_bin)): byte = in_bin[i] - for bitno in range(0,7): + for bitno in range(0, 7): service_nr = i * 8 + bitno + 1 ret[service_nr] = { - 'activated': True if byte & (1 << bitno) else False + 'activated': True if byte & (1 << bitno) else False } if service_nr in self.table: ret[service_nr]['description'] = self.table[service_nr] return ret + def _encode_bin(self, in_json): # compute the required binary size bin_len = 0 for srv in in_json.keys(): service_nr = int(srv) - (byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(service_nr) + (byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service( + service_nr) if byte_offset >= bin_len: bin_len = byte_offset+1 # encode the actual data out = bytearray(b'\x00' * bin_len) for srv in in_json.keys(): service_nr = int(srv) - (byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service(service_nr) + (byte_offset, bit_offset) = EF_UServiceTable._bit_byte_offset_for_service( + service_nr) if in_json[srv]['activated'] == True: bit = 1 else: bit = 0 out[byte_offset] |= (bit) << bit_offset return out + @with_default_category('File-Specific Commands') class AddlShellCommands(CommandSet): def __init__(self): @@ -584,14 +612,18 @@ class EF_UServiceTable(TransparentEF): self._cmd.card.update_ust(int(arg), 0) # TS 31.103 Section 4.2.7 - *not* the same as DF.GSM/EF.ECC! + + class EF_ECC(LinFixedEF): cc_construct = Rpad(BcdAdapter(Rpad(Bytes(3))), pattern='f') category_construct = FlagsEnum(Byte, police=1, ambulance=2, fire_brigade=3, marine_guard=4, mountain_rescue=5, manual_ecall=6, automatic_ecall=7) alpha_construct = GsmStringAdapter(Rpad(GreedyBytes)) + def __init__(self, fid='6fb7', sfid=0x01, name='EF.ECC', desc='Emergency Call Codes'): - super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={4,20}) + super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={4, 20}) + def _decode_record_bin(self, in_bin): # mandatory parts code = in_bin[:3] @@ -599,12 +631,13 @@ class EF_ECC(LinFixedEF): return None svc_category = in_bin[-1:] 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 if len(in_bin) > 4: alpha_id = in_bin[3:-1] ret['alpha_id'] = parse_construct(EF_ECC.alpha_construct, alpha_id) return ret + def _encode_record_bin(self, in_json): if in_json is None: return b'\xff\xff\xff\xff' @@ -617,21 +650,23 @@ class EF_ECC(LinFixedEF): # TS 31.102 Section 4.2.17 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) self._construct = Struct('tmsi'/HexAdapter(Bytes(4)), 'lai'/HexAdapter(Bytes(5)), 'rfu'/Int8ub, 'lu_status'/Int8ub) # TS 31.102 Section 4.2.18 + + class EF_AD(TransparentEF): class OP_MODE(enum.IntEnum): - normal = 0x00 - type_approval = 0x80 - normal_and_specific_facilities = 0x01 - type_approval_and_specific_facilities = 0x81 - maintenance_off_line = 0x02 - cell_test = 0x04 + normal = 0x00 + type_approval = 0x80 + normal_and_specific_facilities = 0x01 + type_approval_and_specific_facilities = 0x81 + maintenance_off_line = 0x02 + 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) self._construct = BitStruct( # Byte 1 @@ -645,15 +680,19 @@ class EF_AD(TransparentEF): ) # TS 31.102 Section 4.2.23 + + class EF_PSLOCI(TransparentEF): - def __init__(self, fid='6f73', sfid=0x0c, name='EF.PSLOCI', desc='PS Location information', size={14,14}): + 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) self._construct = Struct('ptmsi'/HexAdapter(Bytes(4)), 'ptmsi_sig'/HexAdapter(Bytes(3)), 'rai'/HexAdapter(Bytes(6)), 'rau_status'/Int8ub) # TS 31.102 Section 4.2.33 + + class EF_ICI(CyclicEF): - def __init__(self, fid='6f80', sfid=0x14, name='EF.ICI', rec_len={28,48}, + def __init__(self, fid='6f80', sfid=0x14, name='EF.ICI', rec_len={28, 48}, desc='Incoming Call Information'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._construct = Struct('alpha_id'/Bytes(this._.total_len-28), @@ -668,8 +707,10 @@ class EF_ICI(CyclicEF): 'link_to_phonebook'/Bytes(3)) # TS 31.102 Section 4.2.34 + + class EF_OCI(CyclicEF): - def __init__(self, fid='6f81', sfid=0x15, name='EF.OCI', rec_len={27,47}, + def __init__(self, fid='6f81', sfid=0x15, name='EF.OCI', rec_len={27, 47}, desc='Outgoing Call Information'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._construct = Struct('alpha_id'/Bytes(this._.total_len-27), @@ -683,191 +724,254 @@ class EF_OCI(CyclicEF): 'link_to_phonebook'/Bytes(3)) # TS 31.102 Section 4.2.35 + + class EF_ICT(CyclicEF): - def __init__(self, fid='6f82', sfid=None, name='EF.ICT', rec_len={3,3}, + def __init__(self, fid='6f82', sfid=None, name='EF.ICT', rec_len={3, 3}, desc='Incoming Call Timer'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._construct = Struct('accumulated_call_timer'/Int24ub) # TS 31.102 Section 4.2.38 + + class EF_CCP2(LinFixedEF): def __init__(self, fid='6f4f', sfid=0x16, name='EF.CCP2', desc='Capability Configuration Parameters 2'): - super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={15,None}) + super().__init__(fid=fid, sfid=sfid, + name=name, desc=desc, rec_len={15, None}) # TS 31.102 Section 4.2.48 + + class EF_ACL(TransparentEF): - def __init__(self, fid='6f57', sfid=None, name='EF.ACL', size={32,None}, + def __init__(self, fid='6f57', sfid=None, name='EF.ACL', size={32, None}, desc='Access Point Name Control List'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Struct('num_of_apns'/Int8ub, 'tlvs'/GreedyBytes) # TS 31.102 Section 4.2.51 + + class EF_START_HFN(TransparentEF): - def __init__(self, fid='6f5b', sfid=0x0f, name='EF.START-HFN', size={6,6}, + def __init__(self, fid='6f5b', sfid=0x0f, name='EF.START-HFN', size={6, 6}, desc='Initialisation values for Hyperframe number'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Struct('start_cs'/Int24ub, 'start_ps'/Int24ub) # TS 31.102 Section 4.2.52 + + class EF_THRESHOLD(TransparentEF): - def __init__(self, fid='6f5c', sfid=0x10, name='EF.THRESHOLD', size={3,3}, + def __init__(self, fid='6f5c', sfid=0x10, name='EF.THRESHOLD', size={3, 3}, desc='Maximum value of START'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Struct('max_start'/Int24ub) # TS 31.102 Section 4.2.77 + + class EF_VGCSCA(TransRecEF): - def __init__(self, fid='6fd4', sfid=None, name='EF.VGCSCA', size={2,100}, rec_len=2, + def __init__(self, fid='6fd4', sfid=None, name='EF.VGCSCA', size={2, 100}, rec_len=2, desc='Voice Group Call Service Ciphering Algorithm'): 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) # TS 31.102 Section 4.2.79 + + 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'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Struct('rand'/LV, 'b_tid'/LV, 'key_lifetime'/LV) # TS 31.102 Section 4.2.80 + + class EF_MSK(LinFixedEF): def __init__(self, fid='6fd7', sfid=None, name='EF.MSK', desc='MBMS Service Key List'): - super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={20,None}) + super().__init__(fid=fid, sfid=sfid, + name=name, desc=desc, rec_len={20, None}) msk_ts_constr = Struct('msk_id'/Int32ub, 'timestamp_counter'/Int32ub) self._construct = Struct('key_domain_id'/Bytes(3), 'num_msk_id'/Int8ub, 'msk_ids'/msk_ts_constr[this.num_msk_id]) # TS 31.102 Section 4.2.81 + + class EF_MUK(LinFixedEF): class MUK_Idr(BER_TLV_IE, tag=0x80): _construct = HexAdapter(GreedyBytes) + class MUK_Idi(BER_TLV_IE, tag=0x82): _construct = HexAdapter(GreedyBytes) + class MUK_ID(BER_TLV_IE, tag=0xA0, nested=[MUK_Idr, MUK_Idi]): pass + class TimeStampCounter(BER_TLV_IE, tag=0x81): pass + class EF_MUK_Collection(TLV_IE_Collection, nested=[MUK_ID, TimeStampCounter]): pass + def __init__(self, fid='6fd8', sfid=None, name='EF.MUK', desc='MBMS User Key'): - super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={None,None}) + super().__init__(fid=fid, sfid=sfid, name=name, + desc=desc, rec_len={None, None}) self._tlv = EF_MUK.EF_MUK_Collection # TS 31.102 Section 4.2.83 + + class EF_GBANL(LinFixedEF): class NAF_ID(BER_TLV_IE, tag=0x80): _construct = HexAdapter(GreedyBytes) + class B_TID(BER_TLV_IE, tag=0x81): _construct = HexAdapter(GreedyBytes) + class EF_GBANL_Collection(BER_TLV_IE, nested=[NAF_ID, B_TID]): pass + def __init__(self, fid='6fda', sfid=None, name='EF.GBANL', desc='GBA NAF List'): - super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={None,None}) + super().__init__(fid=fid, sfid=sfid, name=name, + desc=desc, rec_len={None, None}) self._tlv = EF_GBANL.EF_GBANL_Collection # TS 31.102 Section 4.2.85 + + class EF_EHPLMNPI(TransparentEF): - def __init__(self, fid='6fdb', sfid=None, name='EF.EHPLMNPI', size={1,1}, + def __init__(self, fid='6fdb', sfid=None, name='EF.EHPLMNPI', size={1, 1}, desc='Equivalent HPLMN Presentation Indication'): 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)) # TS 31.102 Section 4.2.87 + + class EF_NAFKCA(LinFixedEF): class NAF_KeyCentreAddress(BER_TLV_IE, tag=0x80): _construct = HexAdapter(GreedyBytes) def __init__(self, fid='6fdd', sfid=None, name='EF.NAFKCA', rec_len={None, None}, - desc='NAF Key Centre Address'): + desc='NAF Key Centre Address'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._tlv = EF_NAFKCA.NAF_KeyCentreAddress # TS 31.102 Section 4.2.90 + + class EF_NCP_IP(LinFixedEF): class DataDestAddrRange(TLV_IE, tag=0x83): _construct = Struct('type_of_address'/Enum(Byte, IPv4=0x21, IPv6=0x56), 'prefix_length'/Int8ub, 'prefix'/HexAdapter(GreedyBytes)) + class AccessPointName(TLV_IE, tag=0x80): # coded as per TS 23.003 _construct = HexAdapter(GreedyBytes) + class Login(TLV_IE, tag=0x81): # as per SMS DCS TS 23.038 _construct = GsmStringAdapter(GreedyBytes) + class Password(TLV_IE, tag=0x82): # as per SMS DCS TS 23.038 _construct = GsmStringAdapter(GreedyBytes) + class BearerDescription(TLV_IE, tag=0x84): # Bearer descriptionTLV DO as per TS 31.111 pass + class EF_NCP_IP_Collection(TLV_IE_Collection, nested=[AccessPointName, Login, Password, BearerDescription]): pass def __init__(self, fid='6fe2', sfid=None, name='EF.NCP-IP', rec_len={None, None}, - desc='Network Connectivity Parameters for USIM IP connections'): + desc='Network Connectivity Parameters for USIM IP connections'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._tlv = EF_NCP_IP.EF_NCP_IP_Collection # TS 31.102 Section 4.2.91 + + class EF_EPSLOCI(TransparentEF): - def __init__(self, fid='6fe3', sfid=0x1e, name='EF.EPSLOCI', size={18,18}, + def __init__(self, fid='6fe3', sfid=0x1e, name='EF.EPSLOCI', size={18, 18}, desc='EPS Location Information'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) - upd_status_constr = Enum(Byte, updated=0, not_updated=1, roaming_not_allowed=2) + upd_status_constr = Enum( + Byte, updated=0, not_updated=1, roaming_not_allowed=2) self._construct = Struct('guti'/Bytes(12), 'last_visited_registered_tai'/Bytes(5), 'eps_update_status'/upd_status_constr) # TS 31.102 Section 4.2.92 + + class EF_EPSNSC(LinFixedEF): - class KSI_ASME(BER_TLV_IE, tag= 0x80): + class KSI_ASME(BER_TLV_IE, tag=0x80): _construct = Int8ub - class K_ASME(BER_TLV_IE, tag= 0x81): + + class K_ASME(BER_TLV_IE, tag=0x81): _construct = HexAdapter(GreedyBytes) + class UplinkNASCount(BER_TLV_IE, tag=0x82): _construct = Int32ub + class DownlinkNASCount(BER_TLV_IE, tag=0x83): _construct = Int32ub + class IDofNASAlgorithms(BER_TLV_IE, tag=0x84): _construct = HexAdapter(GreedyBytes) + class EPS_NAS_Security_Context(BER_TLV_IE, tag=0xa0, - nested=[KSI_ASME, K_ASME, UplinkNASCount, DownlinkNASCount, - IDofNASAlgorithms]): + nested=[KSI_ASME, K_ASME, UplinkNASCount, DownlinkNASCount, + IDofNASAlgorithms]): pass - def __init__(self,fid='6fe4', sfid=0x18, name='EF.EPSNSC', rec_len={54,128}, - desc='EPS NAS Security Context'): + def __init__(self, fid='6fe4', sfid=0x18, name='EF.EPSNSC', rec_len={54, 128}, + desc='EPS NAS Security Context'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._tlv = EF_EPSNSC.EPS_NAS_Security_Context # TS 31.102 Section 4.2.96 + + class EF_PWS(TransparentEF): - def __init__(self, fid='6fec', sfid=None, name='EF.PWS', desc='Public Warning System', size={1,1}): + def __init__(self, fid='6fec', sfid=None, name='EF.PWS', desc='Public Warning System', size={1, 1}): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) - pws_config = FlagsEnum(Byte, ignore_pws_in_hplmn_and_equivalent=1, ignore_pws_in_vplmn=2) + pws_config = FlagsEnum( + Byte, ignore_pws_in_hplmn_and_equivalent=1, ignore_pws_in_vplmn=2) self._construct = Struct('pws_configuration'/pws_config) # TS 31.102 Section 4.2.101 + + class EF_IPS(CyclicEF): - def __init__(self, fid='6ff1', sfid=None, name='EF.IPS', rec_len={4,4}, + def __init__(self, fid='6ff1', sfid=None, name='EF.IPS', rec_len={4, 4}, desc='IMEI(SV) Pairing Status'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._construct = Struct('status'/PaddedString(2, 'ascii'), 'link_to_ef_ipd'/Int8ub, 'rfu'/Byte) # TS 31.102 Section 4.2.103 + + class EF_ePDGId(TransparentEF): class ePDGId(BER_TLV_IE, tag=0x80, nested=[]): _construct = Struct('type_of_ePDG_address'/Enum(Byte, FQDN=0, IPv4=1, IPv6=2), 'ePDG_address'/Switch(this.type_of_address, - { 'FQDN': GreedyString("utf8"), - 'IPv4': HexAdapter(GreedyBytes), - 'IPv6': HexAdapter(GreedyBytes) })) + {'FQDN': GreedyString("utf8"), + 'IPv4': HexAdapter(GreedyBytes), + 'IPv6': HexAdapter(GreedyBytes)})) + def __init__(self, fid='6ff3', sfid=None, name='EF.eDPDGId', desc='Home ePDG Identifier'): super().__init__(fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_ePDGId.ePDGId # TS 31.102 Section 4.2.106 + + class EF_FromPreferred(TransparentEF): - def __init__(self, fid='6ff7', sfid=None, name='EF.FromPreferred', size={1,1}, + def __init__(self, fid='6ff7', sfid=None, name='EF.FromPreferred', size={1, 1}, desc='From Preferred'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = BitStruct('rfu'/BitsRFU(7), 'from_preferred'/Bit) @@ -877,76 +981,104 @@ class EF_FromPreferred(TransparentEF): ###################################################################### # TS 31.102 Section 4.4.11.2 + + class EF_5GS3GPPLOCI(TransparentEF): - def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size={20,20}, + def __init__(self, fid='4f01', sfid=0x01, name='EF.5GS3GPPLOCI', size={20, 20}, desc='5S 3GP location information'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) - upd_status_constr = Enum(Byte, updated=0, not_updated=1, roaming_not_allowed=2) + upd_status_constr = Enum( + Byte, updated=0, not_updated=1, roaming_not_allowed=2) self._construct = Struct('5g_guti'/Bytes(13), 'last_visited_registered_tai_in_5gs'/Bytes(6), '5gs_update_status'/upd_status_constr) # TS 31.102 Section 4.4.11.7 + + class EF_UAC_AIC(TransparentEF): - def __init__(self, fid='4f06', sfid=0x06, name='EF.UAC_AIC', size={4,4}, + def __init__(self, fid='4f06', sfid=0x06, name='EF.UAC_AIC', size={4, 4}, desc='UAC Access Identities Configuration'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) cfg_constr = FlagsEnum(Byte, multimedia_priority_service=1, - mission_critical_service=2) + mission_critical_service=2) self._construct = Struct('uac_access_id_config'/cfg_constr) # TS 31.102 Section 4.4.11.9 + + class EF_OPL5G(LinFixedEF): def __init__(self, fid='6f08', sfid=0x08, name='EF.OPL5G', desc='5GS Operator PLMN List'): - super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len={10,None}) + super().__init__(fid=fid, sfid=sfid, + name=name, desc=desc, rec_len={10, None}) self._construct = Struct('tai'/Bytes(9), 'pnn_record_id'/Int8ub) # TS 31.102 Section 4.4.11.10 + + class EF_SUPI_NAI(TransparentEF): class NetworkSpecificIdentifier(TLV_IE, tag=0x80): # RFC 7542 encoded as UTF-8 string _construct = GreedyString("utf8") + class GlobalLineIdentifier(TLV_IE, tag=0x81): # TS 23.003 clause 28.16.2 pass + class GlobalCableIdentifier(TLV_IE, tag=0x82): # TS 23.003 clause 28.15.2 pass + class NAI_TLV_Collection(TLV_IE_Collection, - nested=[NetworkSpecificIdentifier, GlobalLineIdentifier, GlobalCableIdentifier]): + nested=[NetworkSpecificIdentifier, GlobalLineIdentifier, GlobalCableIdentifier]): pass def __init__(self, fid='4f09', sfid=0x09, name='EF.SUPI_NAI', - desc='SUPI as Network Access Identifier'): + desc='SUPI as Network Access Identifier'): super().__init__(fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_SUPI_NAI.NAI_TLV_Collection + class EF_TN3GPPSNN(TransparentEF): class ServingNetworkName(BER_TLV_IE, tag=0x80): _construct = GreedyString("utf8") def __init__(self, fid='4f0c', sfid=0x0c, name='EF.TN3GPPSNN', - desc='Trusted non-3GPP Serving network names list'): + desc='Trusted non-3GPP Serving network names list'): super().__init__(fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_TN3GPPSNN.ServingNetworkName # TS 31.102 Section 4.4.5 + + class DF_WLAN(CardDF): def __init__(self, fid='5f40', name='DF.WLAN', desc='Files for WLAN purpose'): super().__init__(fid=fid, name=name, desc=desc) files = [ TransparentEF('4f41', 0x01, 'EF.Pseudo', 'Pseudonym'), - TransparentEF('4f42', 0x02, 'EF.UPLMNWLAN', 'User controlled PLMN selector for I-WLAN Access'), - TransparentEF('4f43', 0x03, 'EF.OPLMNWLAN', 'Operator controlled PLMN selector for I-WLAN Access'), - LinFixedEF('4f44', 0x04, 'EF.UWSIDL', 'User controlled WLAN Specific Identifier List'), - LinFixedEF('4f45', 0x05, 'EF.OWSIDL', 'Operator controlled WLAN Specific Identifier List'), - TransparentEF('4f46', 0x06, 'EF.WRI', 'WLAN Reauthentication Identity'), - LinFixedEF('4f47', 0x07, 'EF.HWSIDL', 'Home I-WLAN Specific Identifier List'), - TransparentEF('4f48', 0x08, 'EF.WEHPLMNPI', 'I-WLAN Equivalent HPLMN Presentation Indication'), - TransparentEF('4f49', 0x09, 'EF.WHPI', 'I-WLAN HPLMN Priority Indication'), - TransparentEF('4f4a', 0x0a, 'EF.WLRPLMN', 'I-WLAN Last Registered PLMN'), - TransparentEF('4f4b', 0x0b, 'EF.HPLMNDAI', 'HPLMN Direct Access Indicator'), - ] + TransparentEF('4f42', 0x02, 'EF.UPLMNWLAN', + 'User controlled PLMN selector for I-WLAN Access'), + TransparentEF('4f43', 0x03, 'EF.OPLMNWLAN', + 'Operator controlled PLMN selector for I-WLAN Access'), + LinFixedEF('4f44', 0x04, 'EF.UWSIDL', + 'User controlled WLAN Specific Identifier List'), + LinFixedEF('4f45', 0x05, 'EF.OWSIDL', + 'Operator controlled WLAN Specific Identifier List'), + TransparentEF('4f46', 0x06, 'EF.WRI', + 'WLAN Reauthentication Identity'), + LinFixedEF('4f47', 0x07, 'EF.HWSIDL', + 'Home I-WLAN Specific Identifier List'), + TransparentEF('4f48', 0x08, 'EF.WEHPLMNPI', + 'I-WLAN Equivalent HPLMN Presentation Indication'), + TransparentEF('4f49', 0x09, 'EF.WHPI', + 'I-WLAN HPLMN Priority Indication'), + TransparentEF('4f4a', 0x0a, 'EF.WLRPLMN', + 'I-WLAN Last Registered PLMN'), + TransparentEF('4f4b', 0x0b, 'EF.HPLMNDAI', + 'HPLMN Direct Access Indicator'), + ] self.add_files(files) # TS 31.102 Section 4.4.6 + + class DF_HNB(CardDF): def __init__(self, fid='5f50', name='DF.HNB', desc='Files for HomeNodeB purpose'): super().__init__(fid=fid, name=name, desc=desc) @@ -957,51 +1089,69 @@ class DF_HNB(CardDF): LinFixedEF('4f04', 0x04, 'EF.OCSGL', 'Operator CSG Lists'), LinFixedEF('4f05', 0x05, 'EF.OCSGT', 'Operator CSG Type'), LinFixedEF('4f06', 0x06, 'EF.OHNBN', 'Operator Home NodeB Name'), - ] + ] self.add_files(files) # TS 31.102 Section 4.4.8 + + class DF_ProSe(CardDF): def __init__(self, fid='5f90', name='DF.ProSe', desc='Files for ProSe purpose'): super().__init__(fid=fid, name=name, desc=desc) files = [ - LinFixedEF('4f01', 0x01, 'EF.PROSE_MON', 'ProSe Monitoring Parameters'), - LinFixedEF('4f02', 0x02, 'EF.PROSE_ANN', 'ProSe Announcing Parameters'), + LinFixedEF('4f01', 0x01, 'EF.PROSE_MON', + 'ProSe Monitoring Parameters'), + LinFixedEF('4f02', 0x02, 'EF.PROSE_ANN', + 'ProSe Announcing Parameters'), LinFixedEF('4f03', 0x03, 'EF.PROSEFUNC', 'HPLMN ProSe Function'), - TransparentEF('4f04', 0x04, 'EF.PROSE_RADIO_COM', 'ProSe Direct Communication Radio Parameters'), - TransparentEF('4f05', 0x05, 'EF.PROSE_RADIO_MON', 'ProSe Direct Discovery Monitoring Radio Parameters'), - TransparentEF('4f06', 0x06, 'EF.PROSE_RADIO_ANN', 'ProSe Direct Discovery Announcing Radio Parameters'), - LinFixedEF('4f07', 0x07, 'EF.PROSE_POLICY', 'ProSe Policy Parameters'), + TransparentEF('4f04', 0x04, 'EF.PROSE_RADIO_COM', + 'ProSe Direct Communication Radio Parameters'), + TransparentEF('4f05', 0x05, 'EF.PROSE_RADIO_MON', + 'ProSe Direct Discovery Monitoring Radio Parameters'), + TransparentEF('4f06', 0x06, 'EF.PROSE_RADIO_ANN', + 'ProSe Direct Discovery Announcing Radio Parameters'), + LinFixedEF('4f07', 0x07, 'EF.PROSE_POLICY', + 'ProSe Policy Parameters'), LinFixedEF('4f08', 0x08, 'EF.PROSE_PLMN', 'ProSe PLMN Parameters'), TransparentEF('4f09', 0x09, 'EF.PROSE_GC', 'ProSe Group Counter'), TransparentEF('4f10', 0x10, 'EF.PST', 'ProSe Service Table'), - TransparentEF('4f11', 0x11, 'EF.UIRC', 'ProSe UsageInformationReportingConfiguration'), - LinFixedEF('4f12', 0x12, 'EF.PROSE_GM_DISCOVERY', 'ProSe Group Member Discovery Parameters'), - LinFixedEF('4f13', 0x13, 'EF.PROSE_RELAY', 'ProSe Relay Parameters'), - TransparentEF('4f14', 0x14, 'EF.PROSE_RELAY_DISCOVERY', 'ProSe Relay Discovery Parameters'), - ] + TransparentEF('4f11', 0x11, 'EF.UIRC', + 'ProSe UsageInformationReportingConfiguration'), + LinFixedEF('4f12', 0x12, 'EF.PROSE_GM_DISCOVERY', + 'ProSe Group Member Discovery Parameters'), + LinFixedEF('4f13', 0x13, 'EF.PROSE_RELAY', + 'ProSe Relay Parameters'), + TransparentEF('4f14', 0x14, 'EF.PROSE_RELAY_DISCOVERY', + 'ProSe Relay Discovery Parameters'), + ] self.add_files(files) + class DF_USIM_5GS(CardDF): def __init__(self, fid='5FC0', name='DF.5GS', desc='5GS related files'): super().__init__(fid=fid, name=name, desc=desc) files = [ - # I'm looking at 31.102 R16.6 - EF_5GS3GPPLOCI(), - EF_5GS3GPPLOCI('4f02', 0x02, 'EF.5GSN3GPPLOCI', '5GS non-3GPP location information'), - EF_5GS3GPPNSC(), - EF_5GS3GPPNSC('4f04', 0x04, 'EF.5GSN3GPPNSC', '5GS non-3GPP Access NAS Security Context'), - EF_5GAUTHKEYS(), - EF_UAC_AIC(), - EF_SUCI_Calc_Info(), - EF_OPL5G(), - EF_SUPI_NAI(), - TransparentEF('4F0A', 0x0a, 'EF.Routing_Indicator', 'Routing Indicator', size={4,4}), - TransparentEF('4F0B', 0x0b, 'EF.URSP', 'UE Route Selector Policies per PLMN'), - EF_TN3GPPSNN(), + # I'm looking at 31.102 R16.6 + EF_5GS3GPPLOCI(), + EF_5GS3GPPLOCI('4f02', 0x02, 'EF.5GSN3GPPLOCI', + '5GS non-3GPP location information'), + EF_5GS3GPPNSC(), + EF_5GS3GPPNSC('4f04', 0x04, 'EF.5GSN3GPPNSC', + '5GS non-3GPP Access NAS Security Context'), + EF_5GAUTHKEYS(), + EF_UAC_AIC(), + EF_SUCI_Calc_Info(), + EF_OPL5G(), + EF_SUPI_NAI(), + TransparentEF('4F0A', 0x0a, 'EF.Routing_Indicator', + 'Routing Indicator', size={4, 4}), + TransparentEF('4F0B', 0x0b, 'EF.URSP', + 'UE Route Selector Policies per PLMN'), + EF_TN3GPPSNN(), ] self.add_files(files) + class ADF_USIM(CardADF): def __init__(self, aid='a0000000871002', name='ADF.USIM', fid=None, sfid=None, desc='USIM Application'): @@ -1010,113 +1160,130 @@ class ADF_USIM(CardADF): self.shell_commands += [self.AddlShellCommands()] files = [ - EF_LI(sfid=0x02), - EF_IMSI(sfid=0x07), - EF_Keys(), - EF_Keys('6f09', 0x09, 'EF.KeysPS', desc='Ciphering and Integrity Keys for PS domain'), - EF_xPLMNwAcT('6f60', 0x0a, 'EF.PLMNwAcT', - 'User controlled PLMN Selector with Access Technology'), - EF_HPPLMN(), - EF_ACMmax(), - EF_UServiceTable('6f38', 0x04, 'EF.UST', 'USIM Service Table', size={1,17}, table=EF_UST_map), - CyclicEF('6f39', None, 'EF.ACM', 'Accumulated call meter', rec_len={3,3}), - TransparentEF('6f3e', None, 'EF.GID1', 'Group Identifier Level 1'), - TransparentEF('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'), - EF_SPN(), - TransparentEF('6f41', None, 'EF.PUCT', 'Price per unit and currency table', size={5,5}), - EF_CBMI(), - EF_ACC(sfid=0x06), - EF_PLMNsel('6f7b', 0x0d, 'EF.FPLMN', 'Forbidden PLMNs', size={12,None}), - EF_LOCI(), - EF_AD(), - EF_CBMID(sfid=0x0e), - EF_ECC(), - EF_CBMIR(), - EF_PSLOCI(), - EF_ADN('6f3b', None, 'EF.FDN', 'Fixed Dialling Numbers'), - EF_SMS('6f3c', None), - EF_MSISDN(), - EF_SMSP(), - EF_SMSS(), - EF_ADN('6f49', None, 'EF.SDN', 'Service Dialling Numbers'), - EF_EXT('6f4b', None, 'EF.EXT2', 'Extension2 (FDN)'), - EF_EXT('6f4c', None, 'EF.EXT3', 'Extension2 (SDN)'), - EF_SMSR(), - EF_ICI(), - EF_OCI(), - EF_ICT(), - EF_ICT('6f83', None, 'EF.OCT', 'Outgoing Call Timer'), - EF_EXT('6f4e', None, 'EF.EXT5', 'Extension5 (ICI/OCI/MSISDN)'), - EF_CCP2(), - EF_eMLPP(), - EF_AAeM(), - # EF_Hiddenkey - EF_ADN('6f4d', None, 'EF.BDN', 'Barred Dialling Numbers'), - EF_EXT('6f55', None, 'EF.EXT4', 'Extension4 (BDN/SSC)'), - EF_CMI(), - EF_UServiceTable('6f56', 0x05, 'EF.EST', 'Enabled Services Table', size={1,None}, table=EF_EST_map), - EF_ACL(), - EF_DCK(), - EF_CNL(), - EF_START_HFN(), - EF_THRESHOLD(), - EF_xPLMNwAcT('6f61', 0x11, 'EF.OPLMNwAcT', - 'User controlled PLMN Selector with Access Technology'), - EF_ARR('6f06', 0x17), - TransparentEF('6fc4', None, 'EF.NETPAR', 'Network Parameters'), - EF_PNN('6fc5', 0x19), - EF_OPL(), - EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'), - EF_MBI(), - EF_MWIS(), - EF_ADN('6fcb', None, 'EF.CFIS', 'Call Forwarding Indication Status'), - EF_EXT('6fcc', None, 'EF.EXT7', 'Extension7 (CFIS)'), - TransparentEF('6fcd', None, 'EF.SPDI', 'Service Provider Display Information'), - EF_MMSN(), - EF_EXT('6fcf', None, 'EF.EXT8', 'Extension8 (MMSN)'), - EF_MMSICP(), - EF_MMSUP(), - EF_MMSUCP(), - EF_NIA(), - EF_VGCS(), - EF_VGCSS(), - EF_VGCS('6fb3', None, 'EF.VBS', 'Voice Broadcast Service'), - EF_VGCSS('6fb4', None, 'EF.VBSS', 'Voice Broadcast Service Status'), - EF_VGCSCA(), - EF_VGCSCA('6fd5', None, 'EF.VBCSCA', 'Voice Broadcast Service Ciphering Algorithm'), - EF_GBABP(), - EF_MSK(), - EF_MUK(), - EF_GBANL(), - EF_PLMNsel('6fd9', 0x1d, 'EF.EHPLMN', 'Equivalent HPLMN', size={12,None}), - EF_EHPLMNPI(), - EF_NAFKCA(), - TransparentEF('6fde', None, 'EF.SPNI', 'Service Provider Name Icon'), - LinFixedEF('6fdf', None, 'EF.PNNI', 'PLMN Network Name Icon'), - EF_NCP_IP(), - EF_EPSLOCI('6fe3', 0x1e, 'EF.EPSLOCI', 'EPS location information'), - EF_EPSNSC(), - TransparentEF('6fe6', None, 'EF.UFC', 'USAT Facility Control', size={1,16}), - TransparentEF('6fe8', None, 'EF.NASCONFIG', 'Non Access Stratum Configuration'), - # UICC IARI (only in cards that have no ISIM) - EF_PWS(), - LinFixedEF('6fed', None, 'EF.FDNURI', 'Fixed Dialling Numbers URI'), - LinFixedEF('6fee', None, 'EF.BDNURI', 'Barred Dialling Numbers URI'), - LinFixedEF('6fef', None, 'EF.SDNURI', 'Service Dialling Numbers URI'), - EF_IPS(), - EF_ePDGId(), - # FIXME: from EF_ePDGSelection onwards - EF_FromPreferred(), - # FIXME: DF_SoLSA - # FIXME: DF_PHONEBOOK - # FIXME: DF_GSM_ACCESS - DF_WLAN(), - DF_HNB(), - DF_ProSe(), - # FIXME: DF_ACDC - # FIXME: DF_TV - DF_USIM_5GS(), - ] + EF_LI(sfid=0x02), + EF_IMSI(sfid=0x07), + EF_Keys(), + EF_Keys('6f09', 0x09, 'EF.KeysPS', + desc='Ciphering and Integrity Keys for PS domain'), + EF_xPLMNwAcT('6f60', 0x0a, 'EF.PLMNwAcT', + 'User controlled PLMN Selector with Access Technology'), + EF_HPPLMN(), + EF_ACMmax(), + EF_UServiceTable('6f38', 0x04, 'EF.UST', 'USIM Service Table', size={ + 1, 17}, table=EF_UST_map), + CyclicEF('6f39', None, 'EF.ACM', + 'Accumulated call meter', rec_len={3, 3}), + TransparentEF('6f3e', None, 'EF.GID1', 'Group Identifier Level 1'), + TransparentEF('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'), + EF_SPN(), + TransparentEF('6f41', None, 'EF.PUCT', + 'Price per unit and currency table', size={5, 5}), + EF_CBMI(), + EF_ACC(sfid=0x06), + EF_PLMNsel('6f7b', 0x0d, 'EF.FPLMN', + 'Forbidden PLMNs', size={12, None}), + EF_LOCI(), + EF_AD(), + EF_CBMID(sfid=0x0e), + EF_ECC(), + EF_CBMIR(), + EF_PSLOCI(), + EF_ADN('6f3b', None, 'EF.FDN', 'Fixed Dialling Numbers'), + EF_SMS('6f3c', None), + EF_MSISDN(), + EF_SMSP(), + EF_SMSS(), + EF_ADN('6f49', None, 'EF.SDN', 'Service Dialling Numbers'), + EF_EXT('6f4b', None, 'EF.EXT2', 'Extension2 (FDN)'), + EF_EXT('6f4c', None, 'EF.EXT3', 'Extension2 (SDN)'), + EF_SMSR(), + EF_ICI(), + EF_OCI(), + EF_ICT(), + EF_ICT('6f83', None, 'EF.OCT', 'Outgoing Call Timer'), + EF_EXT('6f4e', None, 'EF.EXT5', 'Extension5 (ICI/OCI/MSISDN)'), + EF_CCP2(), + EF_eMLPP(), + EF_AAeM(), + # EF_Hiddenkey + EF_ADN('6f4d', None, 'EF.BDN', 'Barred Dialling Numbers'), + EF_EXT('6f55', None, 'EF.EXT4', 'Extension4 (BDN/SSC)'), + EF_CMI(), + EF_UServiceTable('6f56', 0x05, 'EF.EST', 'Enabled Services Table', size={ + 1, None}, table=EF_EST_map), + EF_ACL(), + EF_DCK(), + EF_CNL(), + EF_START_HFN(), + EF_THRESHOLD(), + EF_xPLMNwAcT('6f61', 0x11, 'EF.OPLMNwAcT', + 'User controlled PLMN Selector with Access Technology'), + EF_ARR('6f06', 0x17), + TransparentEF('6fc4', None, 'EF.NETPAR', 'Network Parameters'), + EF_PNN('6fc5', 0x19), + EF_OPL(), + EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'), + EF_MBI(), + EF_MWIS(), + EF_ADN('6fcb', None, 'EF.CFIS', + 'Call Forwarding Indication Status'), + EF_EXT('6fcc', None, 'EF.EXT7', 'Extension7 (CFIS)'), + TransparentEF('6fcd', None, 'EF.SPDI', + 'Service Provider Display Information'), + EF_MMSN(), + EF_EXT('6fcf', None, 'EF.EXT8', 'Extension8 (MMSN)'), + EF_MMSICP(), + EF_MMSUP(), + EF_MMSUCP(), + EF_NIA(), + EF_VGCS(), + EF_VGCSS(), + EF_VGCS('6fb3', None, 'EF.VBS', 'Voice Broadcast Service'), + EF_VGCSS('6fb4', None, 'EF.VBSS', + 'Voice Broadcast Service Status'), + EF_VGCSCA(), + EF_VGCSCA('6fd5', None, 'EF.VBCSCA', + 'Voice Broadcast Service Ciphering Algorithm'), + EF_GBABP(), + EF_MSK(), + EF_MUK(), + EF_GBANL(), + EF_PLMNsel('6fd9', 0x1d, 'EF.EHPLMN', + 'Equivalent HPLMN', size={12, None}), + EF_EHPLMNPI(), + EF_NAFKCA(), + TransparentEF('6fde', None, 'EF.SPNI', + 'Service Provider Name Icon'), + LinFixedEF('6fdf', None, 'EF.PNNI', 'PLMN Network Name Icon'), + EF_NCP_IP(), + EF_EPSLOCI('6fe3', 0x1e, 'EF.EPSLOCI', 'EPS location information'), + EF_EPSNSC(), + TransparentEF('6fe6', None, 'EF.UFC', + 'USAT Facility Control', size={1, 16}), + TransparentEF('6fe8', None, 'EF.NASCONFIG', + 'Non Access Stratum Configuration'), + # UICC IARI (only in cards that have no ISIM) + EF_PWS(), + LinFixedEF('6fed', None, 'EF.FDNURI', + 'Fixed Dialling Numbers URI'), + LinFixedEF('6fee', None, 'EF.BDNURI', + 'Barred Dialling Numbers URI'), + LinFixedEF('6fef', None, 'EF.SDNURI', + 'Service Dialling Numbers URI'), + EF_IPS(), + EF_ePDGId(), + # FIXME: from EF_ePDGSelection onwards + EF_FromPreferred(), + # FIXME: DF_SoLSA + # FIXME: DF_PHONEBOOK + # FIXME: DF_GSM_ACCESS + DF_WLAN(), + DF_HNB(), + DF_ProSe(), + # FIXME: DF_ACDC + # FIXME: DF_TV + DF_USIM_5GS(), + ] self.add_files(files) def decode_select_response(self, data_hex): @@ -1131,6 +1298,7 @@ class ADF_USIM(CardADF): authenticate_parser.add_argument('rand', help='Random challenge') authenticate_parser.add_argument('autn', help='Authentication Nonce') #authenticate_parser.add_argument('--context', help='Authentication context', default='3G') + @cmd2.with_argparser(authenticate_parser) def do_authenticate(self, opts): """Perform Authentication and Key Agreement (AKA).""" @@ -1151,7 +1319,8 @@ class ADF_USIM(CardADF): """Send an ENVELOPE command to the card.""" tpdu_ie = SMS_TPDU() tpdu_ie.from_bytes(h2b(arg)) - dev_ids = DeviceIdentities(decoded={'source_dev_id':'network','dest_dev_id':'uicc'}) + dev_ids = DeviceIdentities( + decoded={'source_dev_id': 'network', 'dest_dev_id': 'uicc'}) sms_dl = SMSPPDownload(children=[dev_ids, tpdu_ie]) (data, sw) = self._cmd.card._scc.envelope(b2h(sms_dl.to_tlv())) self._cmd.poutput('SW: %s, data: %s' % (sw, data)) @@ -1168,6 +1337,7 @@ sw_usim = { } } + class CardApplicationUSIM(CardApplication): def __init__(self): - super().__init__('USIM', adf=ADF_USIM(), sw=sw_usim) + super().__init__('USIM', adf=ADF_USIM(), sw=sw_usim) diff --git a/pySim/ts_31_103.py b/pySim/ts_31_103.py index 6b69990e..ebf18587 100644 --- a/pySim/ts_31_103.py +++ b/pySim/ts_31_103.py @@ -32,129 +32,160 @@ from pySim.ts_102_221 import EF_ARR # Mapping between ISIM Service Number and its description EF_IST_map = { - 1: 'P-CSCF address', - 2: 'Generic Bootstrapping Architecture (GBA)', - 3: 'HTTP Digest', - 4: 'GBA-based Local Key Establishment Mechanism', - 5: 'Support of P-CSCF discovery for IMS Local Break Out', - 6: 'Short Message Storage (SMS)', - 7: 'Short Message Status Reports (SMSR)', - 8: 'Support for SM-over-IP including data download via SMS-PP as defined in TS 31.111 [31]', - 9: 'Communication Control for IMS by ISIM', - 10: 'Support of UICC access to IMS', - 11: 'URI support by UICC', - 12: 'Media Type support', - 13: 'IMS call disconnection cause', - 14: 'URI support for MO SHORT MESSAGE CONTROL', - 15: 'MCPTT', - 16: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111 [31]', - 17: 'From Preferred', - 18: 'IMS configuration data', - 19: 'XCAP Configuration Data', - 20: 'WebRTC URI', - 21: 'MuD and MiD configuration data', + 1: 'P-CSCF address', + 2: 'Generic Bootstrapping Architecture (GBA)', + 3: 'HTTP Digest', + 4: 'GBA-based Local Key Establishment Mechanism', + 5: 'Support of P-CSCF discovery for IMS Local Break Out', + 6: 'Short Message Storage (SMS)', + 7: 'Short Message Status Reports (SMSR)', + 8: 'Support for SM-over-IP including data download via SMS-PP as defined in TS 31.111 [31]', + 9: 'Communication Control for IMS by ISIM', + 10: 'Support of UICC access to IMS', + 11: 'URI support by UICC', + 12: 'Media Type support', + 13: 'IMS call disconnection cause', + 14: 'URI support for MO SHORT MESSAGE CONTROL', + 15: 'MCPTT', + 16: 'URI support for SMS-PP DOWNLOAD as defined in 3GPP TS 31.111 [31]', + 17: 'From Preferred', + 18: 'IMS configuration data', + 19: 'XCAP Configuration Data', + 20: 'WebRTC URI', + 21: 'MuD and MiD configuration data', } EF_ISIM_ADF_map = { - 'IST': '6F07', - 'IMPI': '6F02', - 'DOMAIN': '6F03', - 'IMPU': '6F04', - 'AD': '6FAD', - 'ARR': '6F06', - 'PCSCF': '6F09', - 'GBAP': '6FD5', - 'GBANL': '6FD7', - 'NAFKCA': '6FDD', - 'UICCIARI': '6FE7', - 'SMS': '6F3C', - 'SMSS': '6F43', - 'SMSR': '6F47', - 'SMSP': '6F42', - 'FromPreferred': '6FF7', - 'IMSConfigData': '6FF8', - 'XCAPConfigData': '6FFC', - 'WebRTCURI': '6FFA' + 'IST': '6F07', + 'IMPI': '6F02', + 'DOMAIN': '6F03', + 'IMPU': '6F04', + 'AD': '6FAD', + 'ARR': '6F06', + 'PCSCF': '6F09', + 'GBAP': '6FD5', + 'GBANL': '6FD7', + 'NAFKCA': '6FDD', + 'UICCIARI': '6FE7', + 'SMS': '6F3C', + 'SMSS': '6F43', + 'SMSR': '6F47', + 'SMSP': '6F42', + 'FromPreferred': '6FF7', + 'IMSConfigData': '6FF8', + 'XCAPConfigData': '6FFC', + 'WebRTCURI': '6FFA' } # TS 31.103 Section 4.2.2 + + class EF_IMPI(TransparentEF): class nai(BER_TLV_IE, tag=0x80): _construct = GreedyString("utf8") + def __init__(self, fid='6f02', sfid=0x02, name='EF.IMPI', desc='IMS private user identity'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_IMPI.nai # TS 31.103 Section 4.2.3 + + class EF_DOMAIN(TransparentEF): class domain(BER_TLV_IE, tag=0x80): _construct = GreedyString("utf8") + def __init__(self, fid='6f05', sfid=0x05, name='EF.DOMAIN', desc='Home Network Domain Name'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_DOMAIN.domain # TS 31.103 Section 4.2.4 + + class EF_IMPU(LinFixedEF): class impu(BER_TLV_IE, tag=0x80): _construct = GreedyString("utf8") + def __init__(self, fid='6f04', sfid=0x04, name='EF.IMPU', desc='IMS public user identity'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_IMPU.impu # TS 31.103 Section 4.2.8 + + class EF_PCSCF(LinFixedEF): def __init__(self, fid='6f09', sfid=None, name='EF.P-CSCF', desc='P-CSCF Address'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) + def _decode_record_hex(self, raw_hex): addr, addr_type = dec_addr_tlv(raw_hex) return {"addr": addr, "addr_type": addr_type} + def _encode_record_hex(self, json_in): addr = json_in['addr'] addr_type = json_in['addr_type'] return enc_addr_tlv(addr, addr_type) # TS 31.103 Section 4.2.9 + + class EF_GBABP(TransparentEF): def __init__(self, fid='6fd5', sfid=None, name='EF.GBABP', desc='GBA Bootstrapping'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) # TS 31.103 Section 4.2.10 + + class EF_GBANL(LinFixedEF): def __init__(self, fid='6fd7', sfid=None, name='EF.GBANL', desc='GBA NAF List'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) # TS 31.103 Section 4.2.11 + + class EF_NAFKCA(LinFixedEF): def __init__(self, fid='6fdd', sfid=None, name='EF.NAFKCA', desc='NAF Key Centre Address'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) # TS 31.103 Section 4.2.16 + + class EF_UICCIARI(LinFixedEF): class iari(BER_TLV_IE, tag=0x80): _construct = GreedyString("utf8") + def __init__(self, fid='6fe7', sfid=None, name='EF.UICCIARI', desc='UICC IARI'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_UICCIARI.iari # TS 31.103 Section 4.2.18 + + class EF_IMSConfigData(BerTlvEF): def __init__(self, fid='6ff8', sfid=None, name='EF.IMSConfigData', desc='IMS Configuration Data'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) # TS 31.103 Section 4.2.19 + + class EF_XCAPConfigData(BerTlvEF): def __init__(self, fid='6ffc', sfid=None, name='EF.XCAPConfigData', desc='XCAP Configuration Data'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) # TS 31.103 Section 4.2.20 + + class EF_WebRTCURI(TransparentEF): class uri(BER_TLV_IE, tag=0x80): _construct = GreedyString("utf8") + def __init__(self, fid='6ffa', sfid=None, name='EF.WebRTCURI', desc='WebRTC URI'): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_WebRTCURI.uri # TS 31.103 Section 4.2.21 + + class EF_MuDMiDConfigData(BerTlvEF): def __init__(self, fid='6ffe', sfid=None, name='EF.MuDMiDConfigData', desc='MuD and MiD Configuration Data'): @@ -172,7 +203,8 @@ class ADF_ISIM(CardADF): EF_IMPU(), EF_AD(), EF_ARR('6f06', 0x06), - EF_UServiceTable('6f07', 0x07, 'EF.IST', 'ISIM Service Table', {1,None}, EF_IST_map), + EF_UServiceTable('6f07', 0x07, 'EF.IST', + 'ISIM Service Table', {1, None}, EF_IST_map), EF_PCSCF(), EF_GBABP(), EF_GBANL(), @@ -187,7 +219,7 @@ class ADF_ISIM(CardADF): EF_XCAPConfigData(), EF_WebRTCURI(), EF_MuDMiDConfigData(), - ] + ] self.add_files(files) # add those commands to the general commands of a TransparentEF self.shell_commands += [ADF_USIM.AddlShellCommands()] @@ -195,6 +227,7 @@ class ADF_ISIM(CardADF): def decode_select_response(self, data_hex): return pySim.ts_102_221.CardProfileUICC.decode_select_response(data_hex) + # TS 31.103 Section 7.1 sw_isim = { 'Security management': { @@ -203,6 +236,7 @@ sw_isim = { } } + class CardApplicationISIM(CardApplication): def __init__(self): - super().__init__('ISIM', adf=ADF_ISIM(), sw=sw_isim) + super().__init__('ISIM', adf=ADF_ISIM(), sw=sw_isim) diff --git a/pySim/ts_51_011.py b/pySim/ts_51_011.py index dd7b7881..b46e62d6 100644 --- a/pySim/ts_51_011.py +++ b/pySim/ts_51_011.py @@ -29,321 +29,323 @@ order to describe the files specified in the relevant ETSI + 3GPP specifications # along with this program. If not, see . # +from pySim.profile import match_sim +from pySim.profile import CardProfile +from pySim.filesystem import * +import enum +from pySim.construct import * +from construct import Optional as COptional +from construct import * +from struct import pack, unpack +from typing import Tuple +from pySim.tlv import * +from pySim.utils import * MF_num = '3F00' DF_num = { -'TELECOM': '7F10', + 'TELECOM': '7F10', -'GSM': '7F20', -'IS-41': '7F22', -'FP-CTS': '7F23', + 'GSM': '7F20', + 'IS-41': '7F22', + 'FP-CTS': '7F23', -'GRAPHICS': '5F50', + 'GRAPHICS': '5F50', -'IRIDIUM': '5F30', -'GLOBST': '5F31', -'ICO': '5F32', -'ACeS': '5F33', + 'IRIDIUM': '5F30', + 'GLOBST': '5F31', + 'ICO': '5F32', + 'ACeS': '5F33', -'EIA/TIA-553': '5F40', -'CTS': '5F60', -'SOLSA': '5F70', + 'EIA/TIA-553': '5F40', + 'CTS': '5F60', + 'SOLSA': '5F70', -'MExE': '5F3C', + 'MExE': '5F3C', } EF_num = { -# MF -'ICCID': '2FE2', -'ELP': '2F05', -'DIR': '2F00', + # MF + 'ICCID': '2FE2', + 'ELP': '2F05', + 'DIR': '2F00', -# DF_TELECOM -'ADN': '6F3A', -'FDN': '6F3B', -'SMS': '6F3C', -'CCP': '6F3D', -'MSISDN': '6F40', -'SMSP': '6F42', -'SMSS': '6F43', -'LND': '6F44', -'SMSR': '6F47', -'SDN': '6F49', -'EXT1': '6F4A', -'EXT2': '6F4B', -'EXT3': '6F4C', -'BDN': '6F4D', -'EXT4': '6F4E', -'CMI': '6F58', -'ECCP': '6F4F', + # DF_TELECOM + 'ADN': '6F3A', + 'FDN': '6F3B', + 'SMS': '6F3C', + 'CCP': '6F3D', + 'MSISDN': '6F40', + 'SMSP': '6F42', + 'SMSS': '6F43', + 'LND': '6F44', + 'SMSR': '6F47', + 'SDN': '6F49', + 'EXT1': '6F4A', + 'EXT2': '6F4B', + 'EXT3': '6F4C', + 'BDN': '6F4D', + 'EXT4': '6F4E', + 'CMI': '6F58', + 'ECCP': '6F4F', -# DF_GRAPHICS -'IMG': '4F20', + # DF_GRAPHICS + 'IMG': '4F20', -# DF_SoLSA -'SAI': '4F30', -'SLL': '4F31', + # DF_SoLSA + 'SAI': '4F30', + 'SLL': '4F31', -# DF_MExE -'MExE-ST': '4F40', -'ORPK': '4F41', -'ARPK': '4F42', -'TPRPK': '4F43', + # DF_MExE + 'MExE-ST': '4F40', + 'ORPK': '4F41', + 'ARPK': '4F42', + 'TPRPK': '4F43', -# DF_GSM -'LP': '6F05', -'IMSI': '6F07', -'Kc': '6F20', -'DCK': '6F2C', -'PLMNsel': '6F30', -'HPPLMN': '6F31', -'CNL': '6F32', -'ACMmax': '6F37', -'SST': '6F38', -'ACM': '6F39', -'GID1': '6F3E', -'GID2': '6F3F', -'PUCT': '6F41', -'CBMI': '6F45', -'SPN': '6F46', -'CBMID': '6F48', -'BCCH': '6F74', -'ACC': '6F78', -'FPLMN': '6F7B', -'LOCI': '6F7E', -'AD': '6FAD', -'PHASE': '6FAE', -'VGCS': '6FB1', -'VGCSS': '6FB2', -'VBS': '6FB3', -'VBSS': '6FB4', -'eMLPP': '6FB5', -'AAeM': '6FB6', -'ECC': '6FB7', -'CBMIR': '6F50', -'NIA': '6F51', -'KcGPRS': '6F52', -'LOCIGPRS': '6F53', -'SUME': '6F54', -'PLMNwAcT': '6F60', -'OPLMNwAcT': '6F61', -# Figure 8 names it HPLMNAcT, but in the text it's names it HPLMNwAcT -'HPLMNAcT': '6F62', -'HPLMNwAcT': '6F62', -'CPBCCH': '6F63', -'INVSCAN': '6F64', -'PNN': '6FC5', -'OPL': '6FC6', -'MBDN': '6FC7', -'EXT6': '6FC8', -'MBI': '6FC9', -'MWIS': '6FCA', -'CFIS': '6FCB', -'EXT7': '6FCC', -'SPDI': '6FCD', -'MMSN': '6FCE', -'EXT8': '6FCF', -'MMSICP': '6FD0', -'MMSUP': '6FD1', -'MMSUCP': '6FD2', + # DF_GSM + 'LP': '6F05', + 'IMSI': '6F07', + 'Kc': '6F20', + 'DCK': '6F2C', + 'PLMNsel': '6F30', + 'HPPLMN': '6F31', + 'CNL': '6F32', + 'ACMmax': '6F37', + 'SST': '6F38', + 'ACM': '6F39', + 'GID1': '6F3E', + 'GID2': '6F3F', + 'PUCT': '6F41', + 'CBMI': '6F45', + 'SPN': '6F46', + 'CBMID': '6F48', + 'BCCH': '6F74', + 'ACC': '6F78', + 'FPLMN': '6F7B', + 'LOCI': '6F7E', + 'AD': '6FAD', + 'PHASE': '6FAE', + 'VGCS': '6FB1', + 'VGCSS': '6FB2', + 'VBS': '6FB3', + 'VBSS': '6FB4', + 'eMLPP': '6FB5', + 'AAeM': '6FB6', + 'ECC': '6FB7', + 'CBMIR': '6F50', + 'NIA': '6F51', + 'KcGPRS': '6F52', + 'LOCIGPRS': '6F53', + 'SUME': '6F54', + 'PLMNwAcT': '6F60', + 'OPLMNwAcT': '6F61', + # Figure 8 names it HPLMNAcT, but in the text it's names it HPLMNwAcT + 'HPLMNAcT': '6F62', + 'HPLMNwAcT': '6F62', + 'CPBCCH': '6F63', + 'INVSCAN': '6F64', + 'PNN': '6FC5', + 'OPL': '6FC6', + 'MBDN': '6FC7', + 'EXT6': '6FC8', + 'MBI': '6FC9', + 'MWIS': '6FCA', + 'CFIS': '6FCB', + 'EXT7': '6FCC', + 'SPDI': '6FCD', + 'MMSN': '6FCE', + 'EXT8': '6FCF', + 'MMSICP': '6FD0', + 'MMSUP': '6FD1', + 'MMSUCP': '6FD2', } DF = { -'TELECOM': [MF_num, DF_num['TELECOM']], + 'TELECOM': [MF_num, DF_num['TELECOM']], -'GSM': [MF_num, DF_num['GSM']], -'IS-41': [MF_num, DF_num['IS-41']], -'FP-CTS': [MF_num, DF_num['FP-CTS']], + 'GSM': [MF_num, DF_num['GSM']], + 'IS-41': [MF_num, DF_num['IS-41']], + 'FP-CTS': [MF_num, DF_num['FP-CTS']], -'GRAPHICS': [MF_num, DF_num['GRAPHICS']], + 'GRAPHICS': [MF_num, DF_num['GRAPHICS']], -'IRIDIUM': [MF_num, DF_num['IRIDIUM']], -'GLOBST': [MF_num, DF_num['GLOBST']], -'ICO': [MF_num, DF_num['ICO']], -'ACeS': [MF_num, DF_num['ACeS']], + 'IRIDIUM': [MF_num, DF_num['IRIDIUM']], + 'GLOBST': [MF_num, DF_num['GLOBST']], + 'ICO': [MF_num, DF_num['ICO']], + 'ACeS': [MF_num, DF_num['ACeS']], -'EIA/TIA-553': [MF_num, DF_num['EIA/TIA-553']], -'CTS': [MF_num, DF_num['CTS']], -'SoLSA': [MF_num, DF_num['SOLSA']], + 'EIA/TIA-553': [MF_num, DF_num['EIA/TIA-553']], + 'CTS': [MF_num, DF_num['CTS']], + 'SoLSA': [MF_num, DF_num['SOLSA']], -'MExE': [MF_num, DF_num['MExE']], + 'MExE': [MF_num, DF_num['MExE']], } EF = { -'ICCID': [MF_num, EF_num['ICCID']], -'ELP': [MF_num, EF_num['ELP']], -'DIR': [MF_num, EF_num['DIR']], + 'ICCID': [MF_num, EF_num['ICCID']], + 'ELP': [MF_num, EF_num['ELP']], + 'DIR': [MF_num, EF_num['DIR']], -'ADN': DF['TELECOM']+[EF_num['ADN']], -'FDN': DF['TELECOM']+[EF_num['FDN']], -'SMS': DF['TELECOM']+[EF_num['SMS']], -'CCP': DF['TELECOM']+[EF_num['CCP']], -'MSISDN': DF['TELECOM']+[EF_num['MSISDN']], -'SMSP': DF['TELECOM']+[EF_num['SMSP']], -'SMSS': DF['TELECOM']+[EF_num['SMSS']], -'LND': DF['TELECOM']+[EF_num['LND']], -'SMSR': DF['TELECOM']+[EF_num['SMSR']], -'SDN': DF['TELECOM']+[EF_num['SDN']], -'EXT1': DF['TELECOM']+[EF_num['EXT1']], -'EXT2': DF['TELECOM']+[EF_num['EXT2']], -'EXT3': DF['TELECOM']+[EF_num['EXT3']], -'BDN': DF['TELECOM']+[EF_num['BDN']], -'EXT4': DF['TELECOM']+[EF_num['EXT4']], -'CMI': DF['TELECOM']+[EF_num['CMI']], -'ECCP': DF['TELECOM']+[EF_num['ECCP']], + 'ADN': DF['TELECOM']+[EF_num['ADN']], + 'FDN': DF['TELECOM']+[EF_num['FDN']], + 'SMS': DF['TELECOM']+[EF_num['SMS']], + 'CCP': DF['TELECOM']+[EF_num['CCP']], + 'MSISDN': DF['TELECOM']+[EF_num['MSISDN']], + 'SMSP': DF['TELECOM']+[EF_num['SMSP']], + 'SMSS': DF['TELECOM']+[EF_num['SMSS']], + 'LND': DF['TELECOM']+[EF_num['LND']], + 'SMSR': DF['TELECOM']+[EF_num['SMSR']], + 'SDN': DF['TELECOM']+[EF_num['SDN']], + 'EXT1': DF['TELECOM']+[EF_num['EXT1']], + 'EXT2': DF['TELECOM']+[EF_num['EXT2']], + 'EXT3': DF['TELECOM']+[EF_num['EXT3']], + 'BDN': DF['TELECOM']+[EF_num['BDN']], + 'EXT4': DF['TELECOM']+[EF_num['EXT4']], + 'CMI': DF['TELECOM']+[EF_num['CMI']], + 'ECCP': DF['TELECOM']+[EF_num['ECCP']], -'IMG': DF['GRAPHICS']+[EF_num['IMG']], + 'IMG': DF['GRAPHICS']+[EF_num['IMG']], -'SAI': DF['SoLSA']+[EF_num['SAI']], -'SLL': DF['SoLSA']+[EF_num['SLL']], + 'SAI': DF['SoLSA']+[EF_num['SAI']], + 'SLL': DF['SoLSA']+[EF_num['SLL']], -'MExE-ST': DF['MExE']+[EF_num['MExE-ST']], -'ORPK': DF['MExE']+[EF_num['ORPK']], -'ARPK': DF['MExE']+[EF_num['ARPK']], -'TPRPK': DF['MExE']+[EF_num['TPRPK']], + 'MExE-ST': DF['MExE']+[EF_num['MExE-ST']], + 'ORPK': DF['MExE']+[EF_num['ORPK']], + 'ARPK': DF['MExE']+[EF_num['ARPK']], + 'TPRPK': DF['MExE']+[EF_num['TPRPK']], -'LP': DF['GSM']+[EF_num['LP']], -'IMSI': DF['GSM']+[EF_num['IMSI']], -'Kc': DF['GSM']+[EF_num['Kc']], -'DCK': DF['GSM']+[EF_num['DCK']], -'PLMNsel': DF['GSM']+[EF_num['PLMNsel']], -'HPPLMN': DF['GSM']+[EF_num['HPPLMN']], -'CNL': DF['GSM']+[EF_num['CNL']], -'ACMmax': DF['GSM']+[EF_num['ACMmax']], -'SST': DF['GSM']+[EF_num['SST']], -'ACM': DF['GSM']+[EF_num['ACM']], -'GID1': DF['GSM']+[EF_num['GID1']], -'GID2': DF['GSM']+[EF_num['GID2']], -'PUCT': DF['GSM']+[EF_num['PUCT']], -'CBMI': DF['GSM']+[EF_num['CBMI']], -'SPN': DF['GSM']+[EF_num['SPN']], -'CBMID': DF['GSM']+[EF_num['CBMID']], -'BCCH': DF['GSM']+[EF_num['BCCH']], -'ACC': DF['GSM']+[EF_num['ACC']], -'FPLMN': DF['GSM']+[EF_num['FPLMN']], -'LOCI': DF['GSM']+[EF_num['LOCI']], -'AD': DF['GSM']+[EF_num['AD']], -'PHASE': DF['GSM']+[EF_num['PHASE']], -'VGCS': DF['GSM']+[EF_num['VGCS']], -'VGCSS': DF['GSM']+[EF_num['VGCSS']], -'VBS': DF['GSM']+[EF_num['VBS']], -'VBSS': DF['GSM']+[EF_num['VBSS']], -'eMLPP': DF['GSM']+[EF_num['eMLPP']], -'AAeM': DF['GSM']+[EF_num['AAeM']], -'ECC': DF['GSM']+[EF_num['ECC']], -'CBMIR': DF['GSM']+[EF_num['CBMIR']], -'NIA': DF['GSM']+[EF_num['NIA']], -'KcGPRS': DF['GSM']+[EF_num['KcGPRS']], -'LOCIGPRS': DF['GSM']+[EF_num['LOCIGPRS']], -'SUME': DF['GSM']+[EF_num['SUME']], -'PLMNwAcT': DF['GSM']+[EF_num['PLMNwAcT']], -'OPLMNwAcT': DF['GSM']+[EF_num['OPLMNwAcT']], -# Figure 8 names it HPLMNAcT, but in the text it's names it HPLMNwAcT -'HPLMNAcT': DF['GSM']+[EF_num['HPLMNAcT']], -'HPLMNwAcT': DF['GSM']+[EF_num['HPLMNAcT']], -'CPBCCH': DF['GSM']+[EF_num['CPBCCH']], -'INVSCAN': DF['GSM']+[EF_num['INVSCAN']], -'PNN': DF['GSM']+[EF_num['PNN']], -'OPL': DF['GSM']+[EF_num['OPL']], -'MBDN': DF['GSM']+[EF_num['MBDN']], -'EXT6': DF['GSM']+[EF_num['EXT6']], -'MBI': DF['GSM']+[EF_num['MBI']], -'MWIS': DF['GSM']+[EF_num['MWIS']], -'CFIS': DF['GSM']+[EF_num['CFIS']], -'EXT7': DF['GSM']+[EF_num['EXT7']], -'SPDI': DF['GSM']+[EF_num['SPDI']], -'MMSN': DF['GSM']+[EF_num['MMSN']], -'EXT8': DF['GSM']+[EF_num['EXT8']], -'MMSICP': DF['GSM']+[EF_num['MMSICP']], -'MMSUP': DF['GSM']+[EF_num['MMSUP']], -'MMSUCP': DF['GSM']+[EF_num['MMSUCP']], + 'LP': DF['GSM']+[EF_num['LP']], + 'IMSI': DF['GSM']+[EF_num['IMSI']], + 'Kc': DF['GSM']+[EF_num['Kc']], + 'DCK': DF['GSM']+[EF_num['DCK']], + 'PLMNsel': DF['GSM']+[EF_num['PLMNsel']], + 'HPPLMN': DF['GSM']+[EF_num['HPPLMN']], + 'CNL': DF['GSM']+[EF_num['CNL']], + 'ACMmax': DF['GSM']+[EF_num['ACMmax']], + 'SST': DF['GSM']+[EF_num['SST']], + 'ACM': DF['GSM']+[EF_num['ACM']], + 'GID1': DF['GSM']+[EF_num['GID1']], + 'GID2': DF['GSM']+[EF_num['GID2']], + 'PUCT': DF['GSM']+[EF_num['PUCT']], + 'CBMI': DF['GSM']+[EF_num['CBMI']], + 'SPN': DF['GSM']+[EF_num['SPN']], + 'CBMID': DF['GSM']+[EF_num['CBMID']], + 'BCCH': DF['GSM']+[EF_num['BCCH']], + 'ACC': DF['GSM']+[EF_num['ACC']], + 'FPLMN': DF['GSM']+[EF_num['FPLMN']], + 'LOCI': DF['GSM']+[EF_num['LOCI']], + 'AD': DF['GSM']+[EF_num['AD']], + 'PHASE': DF['GSM']+[EF_num['PHASE']], + 'VGCS': DF['GSM']+[EF_num['VGCS']], + 'VGCSS': DF['GSM']+[EF_num['VGCSS']], + 'VBS': DF['GSM']+[EF_num['VBS']], + 'VBSS': DF['GSM']+[EF_num['VBSS']], + 'eMLPP': DF['GSM']+[EF_num['eMLPP']], + 'AAeM': DF['GSM']+[EF_num['AAeM']], + 'ECC': DF['GSM']+[EF_num['ECC']], + 'CBMIR': DF['GSM']+[EF_num['CBMIR']], + 'NIA': DF['GSM']+[EF_num['NIA']], + 'KcGPRS': DF['GSM']+[EF_num['KcGPRS']], + 'LOCIGPRS': DF['GSM']+[EF_num['LOCIGPRS']], + 'SUME': DF['GSM']+[EF_num['SUME']], + 'PLMNwAcT': DF['GSM']+[EF_num['PLMNwAcT']], + 'OPLMNwAcT': DF['GSM']+[EF_num['OPLMNwAcT']], + # Figure 8 names it HPLMNAcT, but in the text it's names it HPLMNwAcT + 'HPLMNAcT': DF['GSM']+[EF_num['HPLMNAcT']], + 'HPLMNwAcT': DF['GSM']+[EF_num['HPLMNAcT']], + 'CPBCCH': DF['GSM']+[EF_num['CPBCCH']], + 'INVSCAN': DF['GSM']+[EF_num['INVSCAN']], + 'PNN': DF['GSM']+[EF_num['PNN']], + 'OPL': DF['GSM']+[EF_num['OPL']], + 'MBDN': DF['GSM']+[EF_num['MBDN']], + 'EXT6': DF['GSM']+[EF_num['EXT6']], + 'MBI': DF['GSM']+[EF_num['MBI']], + 'MWIS': DF['GSM']+[EF_num['MWIS']], + 'CFIS': DF['GSM']+[EF_num['CFIS']], + 'EXT7': DF['GSM']+[EF_num['EXT7']], + 'SPDI': DF['GSM']+[EF_num['SPDI']], + 'MMSN': DF['GSM']+[EF_num['MMSN']], + 'EXT8': DF['GSM']+[EF_num['EXT8']], + 'MMSICP': DF['GSM']+[EF_num['MMSICP']], + 'MMSUP': DF['GSM']+[EF_num['MMSUP']], + 'MMSUCP': DF['GSM']+[EF_num['MMSUCP']], } # Mapping between SIM Service Number and its description EF_SST_map = { - 1: 'CHV1 disable function', - 2: 'Abbreviated Dialling Numbers (ADN)', - 3: 'Fixed Dialling Numbers (FDN)', - 4: 'Short Message Storage (SMS)', - 5: 'Advice of Charge (AoC)', - 6: 'Capability Configuration Parameters (CCP)', - 7: 'PLMN selector', - 8: 'RFU', - 9: 'MSISDN', - 10: 'Extension1', - 11: 'Extension2', - 12: 'SMS Parameters', - 13: 'Last Number Dialled (LND)', - 14: 'Cell Broadcast Message Identifier', - 15: 'Group Identifier Level 1', - 16: 'Group Identifier Level 2', - 17: 'Service Provider Name', - 18: 'Service Dialling Numbers (SDN)', - 19: 'Extension3', - 20: 'RFU', - 21: 'VGCS Group Identifier List (EFVGCS and EFVGCSS)', - 22: 'VBS Group Identifier List (EFVBS and EFVBSS)', - 23: 'enhanced Multi-Level Precedence and Pre-emption Service', - 24: 'Automatic Answer for eMLPP', - 25: 'Data download via SMS-CB', - 26: 'Data download via SMS-PP', - 27: 'Menu selection', - 28: 'Call control', - 29: 'Proactive SIM', - 30: 'Cell Broadcast Message Identifier Ranges', - 31: 'Barred Dialling Numbers (BDN)', - 32: 'Extension4', - 33: 'De-personalization Control Keys', - 34: 'Co-operative Network List', - 35: 'Short Message Status Reports', - 36: 'Network\'s indication of alerting in the MS', - 37: 'Mobile Originated Short Message control by SIM', - 38: 'GPRS', - 39: 'Image (IMG)', - 40: 'SoLSA (Support of Local Service Area)', - 41: 'USSD string data object supported in Call Control', - 42: 'RUN AT COMMAND command', - 43: 'User controlled PLMN Selector with Access Technology', - 44: 'Operator controlled PLMN Selector with Access Technology', - 45: 'HPLMN Selector with Access Technology', - 46: 'CPBCCH Information', - 47: 'Investigation Scan', - 48: 'Extended Capability Configuration Parameters', - 49: 'MExE', - 50: 'Reserved and shall be ignored', - 51: 'PLMN Network Name', - 52: 'Operator PLMN List', - 53: 'Mailbox Dialling Numbers', - 54: 'Message Waiting Indication Status', - 55: 'Call Forwarding Indication Status', - 56: 'Service Provider Display Information', - 57: 'Multimedia Messaging Service (MMS)', - 58: 'Extension 8', - 59: 'MMS User Connectivity Parameters', + 1: 'CHV1 disable function', + 2: 'Abbreviated Dialling Numbers (ADN)', + 3: 'Fixed Dialling Numbers (FDN)', + 4: 'Short Message Storage (SMS)', + 5: 'Advice of Charge (AoC)', + 6: 'Capability Configuration Parameters (CCP)', + 7: 'PLMN selector', + 8: 'RFU', + 9: 'MSISDN', + 10: 'Extension1', + 11: 'Extension2', + 12: 'SMS Parameters', + 13: 'Last Number Dialled (LND)', + 14: 'Cell Broadcast Message Identifier', + 15: 'Group Identifier Level 1', + 16: 'Group Identifier Level 2', + 17: 'Service Provider Name', + 18: 'Service Dialling Numbers (SDN)', + 19: 'Extension3', + 20: 'RFU', + 21: 'VGCS Group Identifier List (EFVGCS and EFVGCSS)', + 22: 'VBS Group Identifier List (EFVBS and EFVBSS)', + 23: 'enhanced Multi-Level Precedence and Pre-emption Service', + 24: 'Automatic Answer for eMLPP', + 25: 'Data download via SMS-CB', + 26: 'Data download via SMS-PP', + 27: 'Menu selection', + 28: 'Call control', + 29: 'Proactive SIM', + 30: 'Cell Broadcast Message Identifier Ranges', + 31: 'Barred Dialling Numbers (BDN)', + 32: 'Extension4', + 33: 'De-personalization Control Keys', + 34: 'Co-operative Network List', + 35: 'Short Message Status Reports', + 36: 'Network\'s indication of alerting in the MS', + 37: 'Mobile Originated Short Message control by SIM', + 38: 'GPRS', + 39: 'Image (IMG)', + 40: 'SoLSA (Support of Local Service Area)', + 41: 'USSD string data object supported in Call Control', + 42: 'RUN AT COMMAND command', + 43: 'User controlled PLMN Selector with Access Technology', + 44: 'Operator controlled PLMN Selector with Access Technology', + 45: 'HPLMN Selector with Access Technology', + 46: 'CPBCCH Information', + 47: 'Investigation Scan', + 48: 'Extended Capability Configuration Parameters', + 49: 'MExE', + 50: 'Reserved and shall be ignored', + 51: 'PLMN Network Name', + 52: 'Operator PLMN List', + 53: 'Mailbox Dialling Numbers', + 54: 'Message Waiting Indication Status', + 55: 'Call Forwarding Indication Status', + 56: 'Service Provider Display Information', + 57: 'Multimedia Messaging Service (MMS)', + 58: 'Extension 8', + 59: 'MMS User Connectivity Parameters', } -from pySim.utils import * -from pySim.tlv import * -from typing import Tuple -from struct import pack, unpack -from construct import * -from construct import Optional as COptional -from pySim.construct import * -import enum - -from pySim.filesystem import * -from pySim.profile import CardProfile -from pySim.profile import match_sim ###################################################################### # DF.TELECOM ###################################################################### # TS 51.011 Section 10.5.1 + class EF_ADN(LinFixedEF): def __init__(self, fid='6f3a', sfid=None, name='EF.ADN', desc='Abbreviated Dialing Numbers'): - super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={14, 30}) + super().__init__(fid, sfid=sfid, name=name, + desc=desc, rec_len={14, 30}) + def _decode_record_bin(self, raw_bin_data): alpha_id_len = len(raw_bin_data) - 14 alpha_id = raw_bin_data[:alpha_id_len] @@ -352,9 +354,13 @@ class EF_ADN(LinFixedEF): 'dialing_nr': u[2], 'cap_conf_id': u[3], 'ext1_record_id': u[4]} # TS 51.011 Section 10.5.5 + + class EF_SMS(LinFixedEF): def __init__(self, fid='6f3c', sfid=None, name='EF.SMS', desc='Short messages'): - super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={176,176}) + super().__init__(fid, sfid=sfid, name=name, + desc=desc, rec_len={176, 176}) + def _decode_record_bin(self, raw_bin_data): def decode_status(status): if status & 0x01 == 0x00: @@ -384,73 +390,96 @@ class EF_SMS(LinFixedEF): # TS 51.011 Section 10.5.5 class EF_MSISDN(LinFixedEF): def __init__(self, fid='6f40', sfid=None, name='EF.MSISDN', desc='MSISDN'): - super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={15, 34}) + super().__init__(fid, sfid=sfid, name=name, + desc=desc, rec_len={15, 34}) + def _decode_record_hex(self, raw_hex_data): return {'msisdn': dec_msisdn(raw_hex_data)} + def _encode_record_hex(self, abstract): msisdn = abstract['msisdn'] if type(msisdn) == str: encoded_msisdn = enc_msisdn(msisdn) else: - encoded_msisdn = enc_msisdn(msisdn[2],msisdn[0],msisdn[1]) - alpha_identifier = (list(self.rec_len)[0] - len(encoded_msisdn) // 2) * "ff" + encoded_msisdn = enc_msisdn(msisdn[2], msisdn[0], msisdn[1]) + alpha_identifier = (list(self.rec_len)[ + 0] - len(encoded_msisdn) // 2) * "ff" return alpha_identifier + encoded_msisdn # TS 51.011 Section 10.5.6 + + class EF_SMSP(LinFixedEF): def __init__(self, fid='6f42', sfid=None, name='EF.SMSP', desc='Short message service parameters'): - super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len={28, None}) + super().__init__(fid, sfid=sfid, name=name, + desc=desc, rec_len={28, None}) # TS 51.011 Section 10.5.7 + + class EF_SMSS(TransparentEF): class MemCapAdapter(Adapter): def _decode(self, obj, context, path): return False if obj & 1 else True + def _encode(self, obj, context, path): return 0 if obj else 1 - def __init__(self, fid='6f43', sfid=None, name='EF.SMSS', desc='SMS status', size={2,8}): + + def __init__(self, fid='6f43', sfid=None, name='EF.SMSS', desc='SMS status', size={2, 8}): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) - self._construct = Struct('last_used_tpmr'/Int8ub, 'memory_capacity_exceeded'/self.MemCapAdapter(Int8ub)) + self._construct = Struct( + 'last_used_tpmr'/Int8ub, 'memory_capacity_exceeded'/self.MemCapAdapter(Int8ub)) # TS 51.011 Section 10.5.8 + + class EF_SMSR(LinFixedEF): - def __init__(self, fid='6f47', sfid=None, name='EF.SMSR', desc='SMS status reports', rec_len={30,30}): + def __init__(self, fid='6f47', sfid=None, name='EF.SMSR', desc='SMS status reports', rec_len={30, 30}): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) - self._construct = Struct('sms_record_id'/Int8ub, 'sms_status_report'/HexAdapter(Bytes(29))) + self._construct = Struct( + 'sms_record_id'/Int8ub, 'sms_status_report'/HexAdapter(Bytes(29))) + class EF_EXT(LinFixedEF): - def __init__(self, fid, sfid=None, name='EF.EXT', desc='Extension', rec_len={13,13}): + def __init__(self, fid, sfid=None, name='EF.EXT', desc='Extension', rec_len={13, 13}): super().__init__(fid=fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) - self._construct = Struct('record_type'/Int8ub, 'extension_data'/HexAdapter(Bytes(11)), 'identifier'/Int8ub) + self._construct = Struct( + 'record_type'/Int8ub, 'extension_data'/HexAdapter(Bytes(11)), 'identifier'/Int8ub) # TS 51.011 Section 10.5.16 + + class EF_CMI(LinFixedEF): - def __init__(self, fid='6f58', sfid=None, name='EF.CMI', rec_len={2,21}, + def __init__(self, fid='6f58', sfid=None, name='EF.CMI', rec_len={2, 21}, desc='Comparison Method Information'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) - self._construct = Struct('alpha_id'/Bytes(this._.total_len-1), 'comparison_method_id'/Int8ub) + self._construct = Struct( + 'alpha_id'/Bytes(this._.total_len-1), 'comparison_method_id'/Int8ub) + class DF_TELECOM(CardDF): def __init__(self, fid='7f10', name='DF.TELECOM', desc=None): super().__init__(fid=fid, name=name, desc=desc) files = [ - EF_ADN(), - EF_ADN(fid='6f3b', name='EF.FDN', desc='Fixed dialling numbers'), - EF_SMS(), - LinFixedEF(fid='6f3d', name='EF.CCP', desc='Capability Configuration Parameters', rec_len={14,14}), - LinFixedEF(fid='6f4f', name='EF.ECCP', desc='Extended Capability Configuration Parameters', rec_len={15,32}), - EF_MSISDN(), - EF_SMSP(), - EF_SMSS(), - # LND, SDN - EF_EXT('6f4a', None, 'EF.EXT1', 'Extension1 (ADN/SSC)'), - EF_EXT('6f4b', None, 'EF.EXT2', 'Extension2 (FDN/SSC)'), - EF_EXT('6f4c', None, 'EF.EXT3', 'Extension3 (SDN)'), - EF_ADN(fid='6f4d', name='EF.BDN', desc='Barred Dialling Numbers'), - EF_EXT('6f4e', None, 'EF.EXT4', 'Extension4 (BDN/SSC)'), - EF_SMSR(), - EF_CMI(), - ] + EF_ADN(), + EF_ADN(fid='6f3b', name='EF.FDN', desc='Fixed dialling numbers'), + EF_SMS(), + LinFixedEF(fid='6f3d', name='EF.CCP', + desc='Capability Configuration Parameters', rec_len={14, 14}), + LinFixedEF(fid='6f4f', name='EF.ECCP', + desc='Extended Capability Configuration Parameters', rec_len={15, 32}), + EF_MSISDN(), + EF_SMSP(), + EF_SMSS(), + # LND, SDN + EF_EXT('6f4a', None, 'EF.EXT1', 'Extension1 (ADN/SSC)'), + EF_EXT('6f4b', None, 'EF.EXT2', 'Extension2 (FDN/SSC)'), + EF_EXT('6f4c', None, 'EF.EXT3', 'Extension3 (SDN)'), + EF_ADN(fid='6f4d', name='EF.BDN', desc='Barred Dialling Numbers'), + EF_EXT('6f4e', None, 'EF.EXT4', 'Extension4 (BDN/SSC)'), + EF_SMSR(), + EF_CMI(), + ] self.add_files(files) ###################################################################### @@ -458,30 +487,39 @@ class DF_TELECOM(CardDF): ###################################################################### # TS 51.011 Section 10.3.1 + + class EF_LP(TransRecEF): - def __init__(self, fid='6f05', sfid=None, name='EF.LP', size={1,None}, rec_len=1, + def __init__(self, fid='6f05', sfid=None, name='EF.LP', size={1, None}, rec_len=1, desc='Language Preference'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) + def _decode_record_bin(self, in_bin): return b2h(in_bin) + def _encode_record_bin(self, in_json): return h2b(in_json) # TS 51.011 Section 10.3.2 + + class EF_IMSI(TransparentEF): - def __init__(self, fid='6f07', sfid=None, name='EF.IMSI', desc='IMSI', size={9,9}): + def __init__(self, fid='6f07', sfid=None, name='EF.IMSI', desc='IMSI', size={9, 9}): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) # add those commands to the general commands of a TransparentEF self.shell_commands += [self.AddlShellCommands(self)] + def _decode_hex(self, raw_hex): return {'imsi': dec_imsi(raw_hex)} + def _encode_hex(self, abstract): return enc_imsi(abstract['imsi']) + @with_default_category('File-Specific Commands') class AddlShellCommands(CommandSet): - def __init__(self,ef:TransparentEF): + def __init__(self, ef: TransparentEF): super().__init__() - self._ef=ef + self._ef = ef def do_update_imsi_plmn(self, arg: str): """Change the plmn part of the IMSI""" @@ -491,9 +529,11 @@ class EF_IMSI(TransparentEF): if sw == '9000' and len(data['imsi'])-len(plmn) == 10: imsi = data['imsi'] msin = imsi[len(plmn):] - (data, sw) = self._cmd.rs.update_binary_dec({'imsi': plmn+msin}) + (data, sw) = self._cmd.rs.update_binary_dec( + {'imsi': plmn+msin}) if sw == '9000' and data: - self._cmd.poutput_json(self._cmd.rs.selected_file.decode_hex(data)) + self._cmd.poutput_json( + self._cmd.rs.selected_file.decode_hex(data)) else: raise ValueError("PLMN length does not match IMSI length") else: @@ -503,13 +543,15 @@ class EF_IMSI(TransparentEF): # TS 51.011 Section 10.3.4 class EF_PLMNsel(TransRecEF): def __init__(self, fid='6f30', sfid=None, name='EF.PLMNsel', desc='PLMN selector', - size={24,None}, rec_len=3): + size={24, None}, rec_len=3): super().__init__(fid, name=name, sfid=sfid, desc=desc, size=size, rec_len=rec_len) + def _decode_record_hex(self, in_hex): if in_hex[:6] == "ffffff": return None else: return dec_plmn(in_hex) + def _encode_record_hex(self, in_json): if in_json == None: return "ffffff" @@ -517,23 +559,29 @@ class EF_PLMNsel(TransRecEF): return enc_plmn(in_json['mcc'], in_json['mnc']) # TS 51.011 Section 10.3.6 + + class EF_ACMmax(TransparentEF): - def __init__(self, fid='6f37', sfid=None, name='EF.ACMmax', size={3,3}, + def __init__(self, fid='6f37', sfid=None, name='EF.ACMmax', size={3, 3}, desc='ACM maximum value'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Struct('acm_max'/Int24ub) # TS 51.011 Section 10.3.7 + + class EF_ServiceTable(TransparentEF): def __init__(self, fid, sfid, name, desc, size, table): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self.table = table + @staticmethod - def _bit_byte_offset_for_service(service:int) -> Tuple[int, int]: + def _bit_byte_offset_for_service(service: int) -> Tuple[int, int]: i = service - 1 byte_offset = i//4 bit_offset = (i % 4) * 2 return (byte_offset, bit_offset) + def _decode_bin(self, raw_bin): ret = {} for i in range(0, len(raw_bin)*4): @@ -542,24 +590,27 @@ class EF_ServiceTable(TransparentEF): bit_offset = (i % 4) * 2 bits = (byte >> bit_offset) & 3 ret[service_nr] = { - 'description': self.table[service_nr] if service_nr in self.table else None, - 'allocated': True if bits & 1 else False, - 'activated': True if bits & 2 else False, - } + 'description': self.table[service_nr] if service_nr in self.table else None, + 'allocated': True if bits & 1 else False, + 'activated': True if bits & 2 else False, + } return ret + def _encode_bin(self, in_json): # compute the required binary size bin_len = 0 for srv in in_json.keys(): service_nr = int(srv) - (byte_offset, bit_offset) = EF_ServiceTable._bit_byte_offset_for_service(service_nr) + (byte_offset, bit_offset) = EF_ServiceTable._bit_byte_offset_for_service( + service_nr) if byte_offset >= bin_len: bin_len = byte_offset+1 # encode the actual data out = bytearray(b'\x00' * bin_len) for srv in in_json.keys(): service_nr = int(srv) - (byte_offset, bit_offset) = EF_ServiceTable._bit_byte_offset_for_service(service_nr) + (byte_offset, bit_offset) = EF_ServiceTable._bit_byte_offset_for_service( + service_nr) bits = 0 if in_json[srv]['allocated'] == True: bits |= 1 @@ -569,8 +620,10 @@ class EF_ServiceTable(TransparentEF): return out # TS 51.011 Section 10.3.11 + + class EF_SPN(TransparentEF): - def __init__(self, fid='6f46', sfid=None, name='EF.SPN', desc='Service Provider Name', size={17,17}): + def __init__(self, fid='6f46', sfid=None, name='EF.SPN', desc='Service Provider Name', size={17, 17}): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = BitStruct( # Byte 1 @@ -582,42 +635,52 @@ class EF_SPN(TransparentEF): ) # TS 51.011 Section 10.3.13 + + class EF_CBMI(TransRecEF): - def __init__(self, fid='6f45', sfid=None, name='EF.CBMI', size={2,None}, rec_len=2, + def __init__(self, fid='6f45', sfid=None, name='EF.CBMI', size={2, None}, rec_len=2, desc='Cell Broadcast message identifier selection'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) self._construct = GreedyRange(Int16ub) # TS 51.011 Section 10.3.15 + + class EF_ACC(TransparentEF): - def __init__(self, fid='6f78', sfid=None, name='EF.ACC', desc='Access Control Class', size={2,2}): + def __init__(self, fid='6f78', sfid=None, name='EF.ACC', desc='Access Control Class', size={2, 2}): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) + def _decode_bin(self, raw_bin): return {'acc': unpack('!H', raw_bin)[0]} + def _encode_bin(self, abstract): return pack('!H', abstract['acc']) # TS 51.011 Section 10.3.16 + + class EF_LOCI(TransparentEF): - def __init__(self, fid='6f7e', sfid=None, name='EF.LOCI', desc='Location Information', size={11,11}): + def __init__(self, fid='6f7e', sfid=None, name='EF.LOCI', desc='Location Information', size={11, 11}): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Struct('tmsi'/Bytes(4), 'lai'/Bytes(5), 'tmsi_time'/Int8ub, 'lu_status'/Enum(Byte, updated=0, not_updated=1, plmn_not_allowed=2, location_area_not_allowed=3)) # TS 51.011 Section 10.3.18 + + class EF_AD(TransparentEF): class OP_MODE(enum.IntEnum): - normal = 0x00 - type_approval = 0x80 - normal_and_specific_facilities = 0x01 - type_approval_and_specific_facilities = 0x81 - maintenance_off_line = 0x02 - cell_test = 0x04 + normal = 0x00 + type_approval = 0x80 + normal_and_specific_facilities = 0x01 + type_approval_and_specific_facilities = 0x81 + maintenance_off_line = 0x02 + cell_test = 0x04 #OP_MODE_DICT = {int(v) : str(v) for v in EF_AD.OP_MODE} #OP_MODE_DICT_REVERSED = {str(v) : int(v) for v in EF_AD.OP_MODE} - def __init__(self, fid='6fad', sfid=None, name='EF.AD', desc='Administrative Data', size={3,4}): + def __init__(self, fid='6fad', sfid=None, name='EF.AD', desc='Administrative Data', size={3, 4}): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = BitStruct( # Byte 1 @@ -637,59 +700,79 @@ class EF_AD(TransparentEF): ) # TS 51.011 Section 10.3.20 / 10.3.22 + + class EF_VGCS(TransRecEF): - def __init__(self, fid='6fb1', sfid=None, name='EF.VGCS', size={4,200}, rec_len=4, + def __init__(self, fid='6fb1', sfid=None, name='EF.VGCS', size={4, 200}, rec_len=4, desc='Voice Group Call Service'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) self._construct = BcdAdapter(Bytes(4)) # TS 51.011 Section 10.3.21 / 10.3.23 + + class EF_VGCSS(TransparentEF): - def __init__(self, fid='6fb2', sfid=None, name='EF.VGCSS', size={7,7}, + def __init__(self, fid='6fb2', sfid=None, name='EF.VGCSS', size={7, 7}, desc='Voice Group Call Service Status'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) - self._construct = BitStruct('flags'/Bit[50], Padding(6, pattern=b'\xff')) + self._construct = BitStruct( + 'flags'/Bit[50], Padding(6, pattern=b'\xff')) # TS 51.011 Section 10.3.24 + + class EF_eMLPP(TransparentEF): - def __init__(self, fid='6fb5', sfid=None, name='EF.eMLPP', size={2,2}, + def __init__(self, fid='6fb5', sfid=None, name='EF.eMLPP', size={2, 2}, desc='enhanced Multi Level Pre-emption and Priority'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) - FlagsConstruct = FlagsEnum(Byte, A=1, B=2, zero=4, one=8, two=16, three=32, four=64) - self._construct = Struct('levels'/FlagsConstruct, 'fast_call_setup_cond'/FlagsConstruct) + FlagsConstruct = FlagsEnum( + Byte, A=1, B=2, zero=4, one=8, two=16, three=32, four=64) + self._construct = Struct( + 'levels'/FlagsConstruct, 'fast_call_setup_cond'/FlagsConstruct) # TS 51.011 Section 10.3.25 + + class EF_AAeM(TransparentEF): - def __init__(self, fid='6fb6', sfid=None, name='EF.AAeM', size={1,1}, + def __init__(self, fid='6fb6', sfid=None, name='EF.AAeM', size={1, 1}, desc='Automatic Answer for eMLPP Service'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) - FlagsConstruct = FlagsEnum(Byte, A=1, B=2, zero=4, one=8, two=16, three=32, four=64) + FlagsConstruct = FlagsEnum( + Byte, A=1, B=2, zero=4, one=8, two=16, three=32, four=64) self._construct = Struct('auto_answer_prio_levels'/FlagsConstruct) # TS 51.011 Section 10.3.26 + + class EF_CBMID(EF_CBMI): - def __init__(self, fid='6f48', sfid=None, name='EF.CBMID', size={2,None}, rec_len=2, + def __init__(self, fid='6f48', sfid=None, name='EF.CBMID', size={2, None}, rec_len=2, desc='Cell Broadcast Message Identifier for Data Download'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) self._construct = GreedyRange(Int16ub) # TS 51.011 Section 10.3.27 + + class EF_ECC(TransRecEF): - def __init__(self, fid='6fb7', sfid=None, name='EF.ECC', size={3,15}, rec_len=3, + def __init__(self, fid='6fb7', sfid=None, name='EF.ECC', size={3, 15}, rec_len=3, desc='Emergency Call Codes'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) self._construct = GreedyRange(BcdAdapter(Bytes(3))) # TS 51.011 Section 10.3.28 + + class EF_CBMIR(TransRecEF): - def __init__(self, fid='6f50', sfid=None, name='EF.CBMIR', size={4,None}, rec_len=4, + def __init__(self, fid='6f50', sfid=None, name='EF.CBMIR', size={4, None}, rec_len=4, desc='Cell Broadcast message identifier range selection'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) self._construct = GreedyRange(Struct('lower'/Int16ub, 'upper'/Int16ub)) # TS 51.011 Section 10.3.29 + + class EF_DCK(TransparentEF): - def __init__(self, fid='6f2c', sfid=None, name='EF.DCK', size={16,16}, + def __init__(self, fid='6f2c', sfid=None, name='EF.DCK', size={16, 16}, desc='Depersonalisation Control Keys'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Struct('network'/BcdAdapter(Bytes(4)), @@ -697,10 +780,13 @@ class EF_DCK(TransparentEF): 'service_provider'/BcdAdapter(Bytes(4)), 'corporate'/BcdAdapter(Bytes(4))) # TS 51.011 Section 10.3.30 + + class EF_CNL(TransRecEF): - def __init__(self, fid='6f32', sfid=None, name='EF.CNL', size={6,None}, rec_len=6, + def __init__(self, fid='6f32', sfid=None, name='EF.CNL', size={6, None}, rec_len=6, desc='Co-operative Network List'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) + def _decode_record_hex(self, in_hex): (in_plmn, sub, svp, corp) = unpack('!3sBBB', h2b(in_hex)) res = dec_plmn(b2h(in_plmn)) @@ -708,6 +794,7 @@ class EF_CNL(TransRecEF): res['service_provider_id'] = svp res['corporate_id'] = corp return res + def _encode_record_hex(self, in_json): plmn = enc_plmn(in_json['mcc'], in_json['mnc']) return b2h(pack('!3sBBB', @@ -717,35 +804,46 @@ class EF_CNL(TransRecEF): in_json['corporate_id'])) # TS 51.011 Section 10.3.31 + + class EF_NIA(LinFixedEF): - def __init__(self, fid='6f51', sfid=None, name='EF.NIA', rec_len={1,32}, + def __init__(self, fid='6f51', sfid=None, name='EF.NIA', rec_len={1, 32}, desc='Network\'s Indication of Alerting'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) - self._construct = Struct('alerting_category'/Int8ub, 'category'/GreedyBytes) + self._construct = Struct( + 'alerting_category'/Int8ub, 'category'/GreedyBytes) # TS 51.011 Section 10.3.32 + + class EF_Kc(TransparentEF): - def __init__(self, fid='6f20', sfid=None, name='EF.Kc', desc='Ciphering key Kc', size={9,9}): + def __init__(self, fid='6f20', sfid=None, name='EF.Kc', desc='Ciphering key Kc', size={9, 9}): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Struct('kc'/HexAdapter(Bytes(8)), 'cksn'/Int8ub) # TS 51.011 Section 10.3.33 + + class EF_LOCIGPRS(TransparentEF): - def __init__(self, fid='6f53', sfid=None, name='EF.LOCIGPRS', desc='GPRS Location Information', size={14,14}): + def __init__(self, fid='6f53', sfid=None, name='EF.LOCIGPRS', desc='GPRS Location Information', size={14, 14}): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._construct = Struct('ptmsi'/Bytes(4), 'ptmsi_sig'/Int8ub, 'rai'/Bytes(6), 'rau_status'/Enum(Byte, updated=0, not_updated=1, plmn_not_allowed=2, routing_area_not_allowed=3)) # TS 51.011 Section 10.3.35..37 + + class EF_xPLMNwAcT(TransRecEF): - def __init__(self, fid, sfid=None, name=None, desc=None, size={40,None}, rec_len=5): + def __init__(self, fid, sfid=None, name=None, desc=None, size={40, None}, rec_len=5): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) + def _decode_record_hex(self, in_hex): if in_hex[:6] == "ffffff": return None else: return dec_xplmn_w_act(in_hex) + def _encode_record_hex(self, in_json): if in_json == None: return "ffffff0000" @@ -753,6 +851,7 @@ class EF_xPLMNwAcT(TransRecEF): hplmn = enc_plmn(in_json['mcc'], in_json['mnc']) act = self.enc_act(in_json['act']) return hplmn + act + @staticmethod def enc_act(in_list): u16 = 0 @@ -781,52 +880,68 @@ class EF_xPLMNwAcT(TransRecEF): u16 |= 0x0084 elif 'EC-GSM-IuT' in in_list: u16 |= 0x0088 - return '%04X'%(u16) + return '%04X' % (u16) # TS 51.011 Section 10.3.38 + + class EF_CPBCCH(TransRecEF): - def __init__(self, fid='6f63', sfid=None, name='EF.CPBCCH', size={2,14}, rec_len=2, + def __init__(self, fid='6f63', sfid=None, name='EF.CPBCCH', size={2, 14}, rec_len=2, desc='CPBCCH Information'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, rec_len=rec_len) self._construct = Struct('cpbcch'/Int16ub) # TS 51.011 Section 10.3.39 + + class EF_InvScan(TransparentEF): - def __init__(self, fid='6f64', sfid=None, name='EF.InvScan', size={1,1}, + def __init__(self, fid='6f64', sfid=None, name='EF.InvScan', size={1, 1}, desc='IOnvestigation Scan'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) - self._construct = FlagsEnum(Byte, in_limited_service_mode=1, after_successful_plmn_selection=2) + self._construct = FlagsEnum( + Byte, in_limited_service_mode=1, after_successful_plmn_selection=2) # TS 51.011 Section 4.2.58 + + class EF_PNN(LinFixedEF): class FullNameForNetwork(BER_TLV_IE, tag=0x43): # TS 24.008 10.5.3.5a pass + class ShortNameForNetwork(BER_TLV_IE, tag=0x45): # TS 24.008 10.5.3.5a pass + class NetworkNameCollection(TLV_IE_Collection, nested=[FullNameForNetwork, ShortNameForNetwork]): pass + def __init__(self, fid='6fc5', sfid=None, name='EF.PNN', desc='PLMN Network Name'): super().__init__(fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_PNN.NetworkNameCollection # TS 51.011 Section 10.3.42 + + class EF_OPL(LinFixedEF): - def __init__(self, fid='6fc6', sfid=None, name='EF.OPL', rec_len={8,8}, desc='Operator PLMN List'): + def __init__(self, fid='6fc6', sfid=None, name='EF.OPL', rec_len={8, 8}, desc='Operator PLMN List'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._construct = Struct('lai'/Bytes(5), 'pnn_record_id'/Int8ub) # TS 51.011 Section 10.3.44 + TS 31.102 4.2.62 + + class EF_MBI(LinFixedEF): - def __init__(self, fid='6fc9', sfid=None, name='EF.MBI', rec_len={4,5}, desc='Mailbox Identifier'): + def __init__(self, fid='6fc9', sfid=None, name='EF.MBI', rec_len={4, 5}, desc='Mailbox Identifier'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._construct = Struct('mbi_voicemail'/Int8ub, 'mbi_fax'/Int8ub, 'mbi_email'/Int8ub, 'mbi_other'/Int8ub, 'mbi_videocall'/COptional(Int8ub)) # TS 51.011 Section 10.3.45 + TS 31.102 4.2.63 + + class EF_MWIS(LinFixedEF): - def __init__(self, fid='6fca', sfid=None, name='EF.MWIS', rec_len={5,6}, + def __init__(self, fid='6fca', sfid=None, name='EF.MWIS', rec_len={5, 6}, desc='Message Waiting Indication Status'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._construct = Struct('mwi_status'/FlagsEnum(Byte, voicemail=1, fax=2, email=4, other=8, videomail=16), @@ -835,64 +950,82 @@ class EF_MWIS(LinFixedEF): 'num_waiting_other'/Int8ub, 'num_waiting_videomail'/COptional(Int8ub)) # TS 51.011 Section 10.3.66 + + class EF_SPDI(TransparentEF): class ServiceProviderPLMN(BER_TLV_IE, tag=0x80): # flexible numbers of 3-byte PLMN records _construct = GreedyRange(BcdAdapter(Bytes(3))) + class SPDI(BER_TLV_IE, tag=0xA3, nested=[ServiceProviderPLMN]): pass def __init__(self, fid='6fcd', sfid=None, name='EF.SPDI', - desc='Service Provider Display Information'): + desc='Service Provider Display Information'): super().__init__(fid, sfid=sfid, name=name, desc=desc) self._tlv = EF_SPDI.SPDI # TS 51.011 Section 10.3.51 + + class EF_MMSN(LinFixedEF): - def __init__(self, fid='6fce', sfid=None, name='EF.MMSN', rec_len={4,20}, desc='MMS Notification'): + def __init__(self, fid='6fce', sfid=None, name='EF.MMSN', rec_len={4, 20}, desc='MMS Notification'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._construct = Struct('mms_status'/Bytes(2), 'mms_implementation'/Bytes(1), 'mms_notification'/Bytes(this._.total_len-4), 'ext_record_nr'/Byte) # TS 51.011 Annex K.1 + + class MMS_Implementation(BER_TLV_IE, tag=0x80): _construct = FlagsEnum(Byte, WAP=1) # TS 51.011 Section 10.3.53 + + class EF_MMSICP(TransparentEF): class MMS_Relay_Server(BER_TLV_IE, tag=0x81): # 3GPP TS 23.140 pass + class Interface_to_CN(BER_TLV_IE, tag=0x82): # 3GPP TS 23.140 pass + class Gateway(BER_TLV_IE, tag=0x83): # Address, Type of address, Port, Service, AuthType, AuthId, AuthPass / 3GPP TS 23.140 pass + class MMS_ConnectivityParamters(TLV_IE_Collection, - nested=[MMS_Implementation, MMS_Relay_Server, Interface_to_CN, Gateway]): + nested=[MMS_Implementation, MMS_Relay_Server, Interface_to_CN, Gateway]): pass - def __init__(self, fid='6fd0', sfid=None, name='EF.MMSICP', size={1,None}, + def __init__(self, fid='6fd0', sfid=None, name='EF.MMSICP', size={1, None}, desc='MMS Issuer Connectivity Parameters'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) self._tlv = EF_MMSICP.MMS_ConnectivityParamters # TS 51.011 Section 10.3.54 + + class EF_MMSUP(LinFixedEF): class MMS_UserPref_ProfileName(BER_TLV_IE, tag=0x81): pass + class MMS_UserPref_Info(BER_TLV_IE, tag=0x82): pass + class MMS_User_Preferences(TLV_IE_Collection, - nested=[MMS_Implementation,MMS_UserPref_ProfileName,MMS_UserPref_Info]): + nested=[MMS_Implementation, MMS_UserPref_ProfileName, MMS_UserPref_Info]): pass - def __init__(self, fid='6fd1', sfid=None, name='EF.MMSUP', rec_len={1,None}, + def __init__(self, fid='6fd1', sfid=None, name='EF.MMSUP', rec_len={1, None}, desc='MMS User Preferences'): super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=rec_len) self._tlv = EF_MMSUP.MMS_User_Preferences # TS 51.011 Section 10.3.55 + + class EF_MMSUCP(TransparentEF): - def __init__(self, fid='6fd2', sfid=None, name='EF.MMSUCP', size={1,None}, + def __init__(self, fid='6fd2', sfid=None, name='EF.MMSUCP', size={1, None}, desc='MMS User Connectivity Parameters'): super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size) @@ -901,128 +1034,142 @@ class DF_GSM(CardDF): def __init__(self, fid='7f20', name='DF.GSM', desc='GSM Network related files'): super().__init__(fid=fid, name=name, desc=desc) files = [ - EF_LP(), - EF_IMSI(), - EF_Kc(), - EF_PLMNsel(), - TransparentEF('6f31', None, 'EF.HPPLMN', 'Higher Priority PLMN search period'), - EF_ACMmax(), - EF_ServiceTable('6f38', None, 'EF.SST', 'SIM service table', table=EF_SST_map, size={2,16}), - CyclicEF('6f39', None, 'EF.ACM', 'Accumulated call meter', rec_len={3,3}), - TransparentEF('6f3e', None, 'EF.GID1', 'Group Identifier Level 1'), - TransparentEF('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'), - EF_SPN(), - TransparentEF('6f41', None, 'EF.PUCT', 'Price per unit and currency table', size={5,5}), - EF_CBMI(), - TransparentEF('6f7f', None, 'EF.BCCH', 'Broadcast control channels', size={16,16}), - EF_ACC(), - EF_PLMNsel('6f7b', None, 'EF.FPLMN', 'Forbidden PLMNs', size={12,12}), - EF_LOCI(), - EF_AD(), - TransparentEF('6fa3', None, 'EF.Phase', 'Phase identification', size={1,1}), - EF_VGCS(), - EF_VGCSS(), - EF_VGCS('6fb3', None, 'EF.VBS', 'Voice Broadcast Service'), - EF_VGCSS('6fb4', None, 'EF.VBSS', 'Voice Broadcast Service Status'), - EF_eMLPP(), - EF_AAeM(), - EF_CBMID(), - EF_ECC(), - EF_CBMIR(), - EF_DCK(), - EF_CNL(), - EF_NIA(), - EF_Kc('6f52', None, 'EF.KcGPRS', 'GPRS Ciphering key KcGPRS'), - EF_LOCIGPRS(), - TransparentEF('6f54', None, 'EF.SUME', 'SetUpMenu Elements'), - EF_xPLMNwAcT('6f60', None, 'EF.PLMNwAcT', - 'User controlled PLMN Selector with Access Technology'), - EF_xPLMNwAcT('6f61', None, 'EF.OPLMNwAcT', - 'Operator controlled PLMN Selector with Access Technology'), - EF_xPLMNwAcT('6f62', None, 'EF.HPLMNwAcT', 'HPLMN Selector with Access Technology'), - EF_CPBCCH(), - EF_InvScan(), - EF_PNN(), - EF_OPL(), - EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'), - EF_MBI(), - EF_MWIS(), - EF_ADN('6fcb', None, 'EF.CFIS', 'Call Forwarding Indication Status'), - EF_EXT('6fc8', None, 'EF.EXT6', 'Externsion6 (MBDN)'), - EF_EXT('6fcc', None, 'EF.EXT7', 'Externsion7 (CFIS)'), - EF_SPDI(), - EF_MMSN(), - EF_EXT('6fcf', None, 'EF.EXT8', 'Extension8 (MMSN)'), - EF_MMSICP(), - EF_MMSUP(), - EF_MMSUCP(), - ] + EF_LP(), + EF_IMSI(), + EF_Kc(), + EF_PLMNsel(), + TransparentEF('6f31', None, 'EF.HPPLMN', + 'Higher Priority PLMN search period'), + EF_ACMmax(), + EF_ServiceTable('6f38', None, 'EF.SST', + 'SIM service table', table=EF_SST_map, size={2, 16}), + CyclicEF('6f39', None, 'EF.ACM', + 'Accumulated call meter', rec_len={3, 3}), + TransparentEF('6f3e', None, 'EF.GID1', 'Group Identifier Level 1'), + TransparentEF('6f3f', None, 'EF.GID2', 'Group Identifier Level 2'), + EF_SPN(), + TransparentEF('6f41', None, 'EF.PUCT', + 'Price per unit and currency table', size={5, 5}), + EF_CBMI(), + TransparentEF('6f7f', None, 'EF.BCCH', + 'Broadcast control channels', size={16, 16}), + EF_ACC(), + EF_PLMNsel('6f7b', None, 'EF.FPLMN', + 'Forbidden PLMNs', size={12, 12}), + EF_LOCI(), + EF_AD(), + TransparentEF('6fa3', None, 'EF.Phase', + 'Phase identification', size={1, 1}), + EF_VGCS(), + EF_VGCSS(), + EF_VGCS('6fb3', None, 'EF.VBS', 'Voice Broadcast Service'), + EF_VGCSS('6fb4', None, 'EF.VBSS', + 'Voice Broadcast Service Status'), + EF_eMLPP(), + EF_AAeM(), + EF_CBMID(), + EF_ECC(), + EF_CBMIR(), + EF_DCK(), + EF_CNL(), + EF_NIA(), + EF_Kc('6f52', None, 'EF.KcGPRS', 'GPRS Ciphering key KcGPRS'), + EF_LOCIGPRS(), + TransparentEF('6f54', None, 'EF.SUME', 'SetUpMenu Elements'), + EF_xPLMNwAcT('6f60', None, 'EF.PLMNwAcT', + 'User controlled PLMN Selector with Access Technology'), + EF_xPLMNwAcT('6f61', None, 'EF.OPLMNwAcT', + 'Operator controlled PLMN Selector with Access Technology'), + EF_xPLMNwAcT('6f62', None, 'EF.HPLMNwAcT', + 'HPLMN Selector with Access Technology'), + EF_CPBCCH(), + EF_InvScan(), + EF_PNN(), + EF_OPL(), + EF_ADN('6fc7', None, 'EF.MBDN', 'Mailbox Dialling Numbers'), + EF_MBI(), + EF_MWIS(), + EF_ADN('6fcb', None, 'EF.CFIS', + 'Call Forwarding Indication Status'), + EF_EXT('6fc8', None, 'EF.EXT6', 'Externsion6 (MBDN)'), + EF_EXT('6fcc', None, 'EF.EXT7', 'Externsion7 (CFIS)'), + EF_SPDI(), + EF_MMSN(), + EF_EXT('6fcf', None, 'EF.EXT8', 'Extension8 (MMSN)'), + EF_MMSICP(), + EF_MMSUP(), + EF_MMSUCP(), + ] self.add_files(files) + class CardProfileSIM(CardProfile): ORDER = 2 def __init__(self): sw = { - 'Normal': { - '9000': 'Normal ending of the command', - '91xx': 'normal ending of the command, with extra information from the proactive SIM containing a command for the ME', - '9exx': 'length XX of the response data given in case of a SIM data download error', - '9fxx': 'length XX of the response data', + 'Normal': { + '9000': 'Normal ending of the command', + '91xx': 'normal ending of the command, with extra information from the proactive SIM containing a command for the ME', + '9exx': 'length XX of the response data given in case of a SIM data download error', + '9fxx': 'length XX of the response data', }, - 'Postponed processing': { - '9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed', + 'Postponed processing': { + '9300': 'SIM Application Toolkit is busy. Command cannot be executed at present, further normal commands are allowed', }, - 'Memory management': { - '920x': 'command successful but after using an internal update retry routine X times', - '9240': 'memory problem', + 'Memory management': { + '920x': 'command successful but after using an internal update retry routine X times', + '9240': 'memory problem', }, - 'Referencing management': { - '9400': 'no EF selected', - '9402': 'out of range (invalid address)', - '9404': 'file ID not found or pattern not found', - '9408': 'file is inconsistent with the command', + 'Referencing management': { + '9400': 'no EF selected', + '9402': 'out of range (invalid address)', + '9404': 'file ID not found or pattern not found', + '9408': 'file is inconsistent with the command', }, - 'Security management': { - '9802': 'no CHV initialized', - '9804': 'access condition not fulfilled, unsuccessful CHV verification or authentication failed', - '9808': 'in contradiction with CHV status', - '9810': 'in contradiction with invalidation status', - '9840': 'unsuccessful verification, CHV blocked, UNBLOCK CHV blocked', - '9850': 'increase cannot be performed, Max value reached', + 'Security management': { + '9802': 'no CHV initialized', + '9804': 'access condition not fulfilled, unsuccessful CHV verification or authentication failed', + '9808': 'in contradiction with CHV status', + '9810': 'in contradiction with invalidation status', + '9840': 'unsuccessful verification, CHV blocked, UNBLOCK CHV blocked', + '9850': 'increase cannot be performed, Max value reached', }, - 'Application independent errors': { - '67xx': 'incorrect parameter P3', - '6bxx': 'incorrect parameter P1 or P2', - '6dxx': 'unknown instruction code given in the command', - '6exx': 'wrong instruction class given in the command', - '6fxx': 'technical problem with no diagnostic given', + 'Application independent errors': { + '67xx': 'incorrect parameter P3', + '6bxx': 'incorrect parameter P1 or P2', + '6dxx': 'unknown instruction code given in the command', + '6exx': 'wrong instruction class given in the command', + '6fxx': 'technical problem with no diagnostic given', }, - } + } - super().__init__('SIM', desc='GSM SIM Card', cla="a0", sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM()], sw=sw) + super().__init__('SIM', desc='GSM SIM Card', cla="a0", + sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM()], sw=sw) @staticmethod - def decode_select_response(resp_hex:str) -> object: + def decode_select_response(resp_hex: str) -> object: resp_bin = h2b(resp_hex) struct_of_file_map = { 0: 'transparent', 1: 'linear_fixed', 3: 'cyclic' - } + } type_of_file_map = { 1: 'mf', 2: 'df', 4: 'working_ef' - } + } ret = { 'file_descriptor': {}, 'proprietary_info': {}, - } + } ret['file_id'] = b2h(resp_bin[4:6]) - ret['proprietary_info']['available_memory'] = int.from_bytes(resp_bin[2:4], 'big') - file_type = type_of_file_map[resp_bin[6]] if resp_bin[6] in type_of_file_map else resp_bin[6] + ret['proprietary_info']['available_memory'] = int.from_bytes( + resp_bin[2:4], 'big') + file_type = type_of_file_map[resp_bin[6] + ] if resp_bin[6] in type_of_file_map else resp_bin[6] ret['file_descriptor']['file_type'] = file_type if file_type in ['mf', 'df']: ret['file_characteristics'] = b2h(resp_bin[13:14]) @@ -1031,7 +1178,8 @@ class CardProfileSIM(CardProfile): ret['num_chv_unblock_adm_codes'] = int(resp_bin[16]) # CHV / UNBLOCK CHV stats elif file_type in ['working_ef']: - file_struct = struct_of_file_map[resp_bin[13]] if resp_bin[13] in struct_of_file_map else resp_bin[13] + file_struct = struct_of_file_map[resp_bin[13] + ] if resp_bin[13] in struct_of_file_map else resp_bin[13] ret['file_descriptor']['structure'] = file_struct ret['access_conditions'] = b2h(resp_bin[8:10]) if resp_bin[11] & 0x01 == 0: @@ -1043,5 +1191,5 @@ class CardProfileSIM(CardProfile): return ret @staticmethod - def match_with_card(scc:SimCardCommands) -> bool: + def match_with_card(scc: SimCardCommands) -> bool: return match_sim(scc) diff --git a/pySim/utils.py b/pySim/utils.py index 3e79034d..3ce3e585 100644 --- a/pySim/utils.py +++ b/pySim/utils.py @@ -29,88 +29,102 @@ from typing import Optional, List, Dict, Any, Tuple # just to differentiate strings of hex nibbles from everything else Hexstr = str -def h2b(s:Hexstr) -> bytearray: - """convert from a string of hex nibbles to a sequence of bytes""" - return bytearray.fromhex(s) -def b2h(b:bytearray) -> Hexstr: - """convert from a sequence of bytes to a string of hex nibbles""" - return ''.join(['%02x'%(x) for x in b]) +def h2b(s: Hexstr) -> bytearray: + """convert from a string of hex nibbles to a sequence of bytes""" + return bytearray.fromhex(s) -def h2i(s:Hexstr) -> List[int]: - """convert from a string of hex nibbles to a list of integers""" - return [(int(x,16)<<4)+int(y,16) for x,y in zip(s[0::2], s[1::2])] -def i2h(s:List[int]) -> Hexstr: - """convert from a list of integers to a string of hex nibbles""" - return ''.join(['%02x'%(x) for x in s]) +def b2h(b: bytearray) -> Hexstr: + """convert from a sequence of bytes to a string of hex nibbles""" + return ''.join(['%02x' % (x) for x in b]) -def h2s(s:Hexstr) -> str: - """convert from a string of hex nibbles to an ASCII string""" - return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2]) - if int(x + y, 16) != 0xff]) -def s2h(s:str) -> Hexstr: - """convert from an ASCII string to a string of hex nibbles""" - b = bytearray() - b.extend(map(ord, s)) - return b2h(b) +def h2i(s: Hexstr) -> List[int]: + """convert from a string of hex nibbles to a list of integers""" + return [(int(x, 16) << 4)+int(y, 16) for x, y in zip(s[0::2], s[1::2])] -def i2s(s:List[int]) -> str: - """convert from a list of integers to an ASCII string""" - return ''.join([chr(x) for x in s]) -def swap_nibbles(s:Hexstr) -> Hexstr: - """swap the nibbles in a hex string""" - return ''.join([x+y for x,y in zip(s[1::2], s[0::2])]) +def i2h(s: List[int]) -> Hexstr: + """convert from a list of integers to a string of hex nibbles""" + return ''.join(['%02x' % (x) for x in s]) -def rpad(s:str, l:int, c='f') -> str: - """pad string on the right side. - Args: - s : string to pad - l : total length to pad to - c : padding character - Returns: - String 's' padded with as many 'c' as needed to reach total length of 'l' - """ - return s + c * (l - len(s)) -def lpad(s:str, l:int, c='f') -> str: - """pad string on the left side. - Args: - s : string to pad - l : total length to pad to - c : padding character - Returns: - String 's' padded with as many 'c' as needed to reach total length of 'l' - """ - return c * (l - len(s)) + s +def h2s(s: Hexstr) -> str: + """convert from a string of hex nibbles to an ASCII string""" + return ''.join([chr((int(x, 16) << 4)+int(y, 16)) for x, y in zip(s[0::2], s[1::2]) + if int(x + y, 16) != 0xff]) -def half_round_up(n:int) -> int: - return (n + 1)//2 -def str_sanitize(s:str) -> str: - """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that - there are no whitespaces at the end and at the beginning of the string. +def s2h(s: str) -> Hexstr: + """convert from an ASCII string to a string of hex nibbles""" + b = bytearray() + b.extend(map(ord, s)) + return b2h(b) - Args: - s : string to sanitize - Returns: - filtered result of string 's' - """ - chars_to_keep = string.digits + string.ascii_letters + string.punctuation - res = ''.join([c if c in chars_to_keep else ' ' for c in s]) - return res.strip() +def i2s(s: List[int]) -> str: + """convert from a list of integers to an ASCII string""" + return ''.join([chr(x) for x in s]) + + +def swap_nibbles(s: Hexstr) -> Hexstr: + """swap the nibbles in a hex string""" + return ''.join([x+y for x, y in zip(s[1::2], s[0::2])]) + + +def rpad(s: str, l: int, c='f') -> str: + """pad string on the right side. + Args: + s : string to pad + l : total length to pad to + c : padding character + Returns: + String 's' padded with as many 'c' as needed to reach total length of 'l' + """ + return s + c * (l - len(s)) + + +def lpad(s: str, l: int, c='f') -> str: + """pad string on the left side. + Args: + s : string to pad + l : total length to pad to + c : padding character + Returns: + String 's' padded with as many 'c' as needed to reach total length of 'l' + """ + return c * (l - len(s)) + s + + +def half_round_up(n: int) -> int: + return (n + 1)//2 + + +def str_sanitize(s: str) -> str: + """replace all non printable chars, line breaks and whitespaces, with ' ', make sure that + there are no whitespaces at the end and at the beginning of the string. + + Args: + s : string to sanitize + Returns: + filtered result of string 's' + """ + + chars_to_keep = string.digits + string.ascii_letters + string.punctuation + res = ''.join([c if c in chars_to_keep else ' ' for c in s]) + return res.strip() ######################################################################### # 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""" if binary[0] in [0x00, 0x80, 0xff]: - raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary)) + raise ValueError("Found illegal value 0x%02x in %s" % + (binary[0], binary)) if binary[0] == 0x7f: # three-byte tag tag = binary[0] << 16 | binary[1] << 8 | binary[2] @@ -122,10 +136,12 @@ def comprehensiontlv_parse_tag_raw(binary:bytes) -> Tuple[int, bytes]: tag = binary[0] return (tag, binary[1:]) -def comprehensiontlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]: + +def comprehensiontlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]: """Parse a single Tag according to ETSI TS 101 220 Section 7.1.1""" if binary[0] in [0x00, 0x80, 0xff]: - raise ValueError("Found illegal value 0x%02x in %s" % (binary[0], binary)) + raise ValueError("Found illegal value 0x%02x in %s" % + (binary[0], binary)) if binary[0] == 0x7f: # three-byte tag tag = (binary[1] & 0x7f) << 8 @@ -138,6 +154,7 @@ def comprehensiontlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]: compr = True if binary[0] & 0x80 else False return ({'comprehension': compr, 'tag': tag}, binary[1:]) + def comprehensiontlv_encode_tag(tag) -> bytes: """Encode a single Tag according to ETSI TS 101 220 Section 7.1.1""" # permit caller to specify tag also as integer value @@ -161,76 +178,78 @@ def comprehensiontlv_encode_tag(tag) -> bytes: # length value coding is equal to BER-TLV -def comprehensiontlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]: - """Parse a single TLV IE at the start of the given binary data. - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of (tag:dict, len:int, remainder:bytes) - """ - (tagdict, remainder) = comprehensiontlv_parse_tag(binary) - (length, remainder) = bertlv_parse_len(remainder) - value = remainder[:length] - remainder = remainder[length:] - return (tagdict, length, value, remainder) +def comprehensiontlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]: + """Parse a single TLV IE at the start of the given binary data. + Args: + binary : binary input data of BER-TLV length field + Returns: + Tuple of (tag:dict, len:int, remainder:bytes) + """ + (tagdict, remainder) = comprehensiontlv_parse_tag(binary) + (length, remainder) = bertlv_parse_len(remainder) + value = remainder[:length] + remainder = remainder[length:] + return (tagdict, length, value, remainder) ######################################################################### # poor man's BER-TLV decoder. To be a more sophisticated OO library later ######################################################################### -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 - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of (tag:int, remainder:bytes) - """ - # check for FF padding at the end, as customary in SIM card files - if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff: - return None, binary - tag = binary[0] & 0x1f - if tag <= 30: - return binary[0], binary[1:] - else: # multi-byte tag - tag = binary[0] - i = 1 - last = False - while not last: - last = False if binary[i] & 0x80 else True - tag <<= 8 - tag |= binary[i] - i += 1 - return tag, binary[i:] +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 + Args: + binary : binary input data of BER-TLV length field + Returns: + Tuple of (tag:int, remainder:bytes) + """ + # check for FF padding at the end, as customary in SIM card files + if binary[0] == 0xff and len(binary) == 1 or binary[0] == 0xff and binary[1] == 0xff: + return None, binary + tag = binary[0] & 0x1f + if tag <= 30: + return binary[0], binary[1:] + else: # multi-byte tag + tag = binary[0] + i = 1 + last = False + while not last: + last = False if binary[i] & 0x80 else True + tag <<= 8 + tag |= binary[i] + i += 1 + return tag, binary[i:] + + +def bertlv_parse_tag(binary: bytes) -> Tuple[dict, bytes]: + """Parse a single Tag value according to ITU-T X.690 8.1.2 + Args: + binary : binary input data of BER-TLV length field + Returns: + Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes) + """ + cls = binary[0] >> 6 + constructed = True if binary[0] & 0x20 else False + tag = binary[0] & 0x1f + if tag <= 30: + return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[1:]) + else: # multi-byte tag + tag = 0 + i = 1 + last = False + while not last: + last = False if binary[i] & 0x80 else True + tag <<= 7 + tag |= binary[i] & 0x7f + i += 1 + return ({'class': cls, 'constructed': constructed, 'tag': tag}, binary[i:]) -def bertlv_parse_tag(binary:bytes) -> Tuple[dict, bytes]: - """Parse a single Tag value according to ITU-T X.690 8.1.2 - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of ({class:int, constructed:bool, tag:int}, remainder:bytes) - """ - cls = binary[0] >> 6 - constructed = True if binary[0] & 0x20 else False - tag = binary[0] & 0x1f - if tag <= 30: - return ({'class':cls, 'constructed':constructed, 'tag': tag}, binary[1:]) - else: # multi-byte tag - tag = 0 - i = 1 - last = False - while not last: - last = False if binary[i] & 0x80 else True - tag <<= 7 - tag |= binary[i] & 0x7f - i += 1 - return ({'class':cls, 'constructed':constructed, 'tag':tag}, binary[i:]) def bertlv_encode_tag(t) -> bytes: """Encode a single Tag value according to ITU-T X.690 8.1.2 """ - 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.""" remain_bits = inp.bit_length() if remain_bits >= 7: @@ -256,7 +275,7 @@ def bertlv_encode_tag(t) -> bytes: t |= 0x20 t |= (cls & 3) << 6 return bytes([t]) - else: # multi-byte tag + else: # multi-byte tag t = 0x1f if constructed: t |= 0x20 @@ -272,58 +291,60 @@ def bertlv_encode_tag(t) -> bytes: break return tag_bytes -def bertlv_parse_len(binary:bytes) -> Tuple[int, bytes]: - """Parse a single Length value according to ITU-T X.690 8.1.3; - only the definite form is supported here. - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of (length, remainder) - """ - if binary[0] < 0x80: - return (binary[0], binary[1:]) - else: - num_len_oct = binary[0] & 0x7f - length = 0 - for i in range(1, 1+num_len_oct): - length <<= 8 - length |= binary[i] - return (length, binary[1+num_len_oct:]) -def bertlv_encode_len(length:int) -> bytes: - """Encode a single Length value according to ITU-T X.690 8.1.3; - only the definite form is supported here. - Args: - length : length value to be encoded - Returns: - binary output data of BER-TLV length field - """ - if length < 0x80: - return length.to_bytes(1, 'big') - elif length <= 0xff: - return b'\x81' + length.to_bytes(1, 'big') - elif length <= 0xffff: - return b'\x82' + length.to_bytes(2, 'big') - elif length <= 0xffffff: - return b'\x83' + length.to_bytes(3, 'big') - elif length <= 0xffffffff: - return b'\x84' + length.to_bytes(4, 'big') - else: - raise ValueError("Length > 32bits not supported") +def bertlv_parse_len(binary: bytes) -> Tuple[int, bytes]: + """Parse a single Length value according to ITU-T X.690 8.1.3; + only the definite form is supported here. + Args: + binary : binary input data of BER-TLV length field + Returns: + Tuple of (length, remainder) + """ + if binary[0] < 0x80: + return (binary[0], binary[1:]) + else: + num_len_oct = binary[0] & 0x7f + length = 0 + for i in range(1, 1+num_len_oct): + length <<= 8 + length |= binary[i] + return (length, binary[1+num_len_oct:]) -def bertlv_parse_one(binary:bytes) -> Tuple[dict, int, bytes, bytes]: - """Parse a single TLV IE at the start of the given binary data. - Args: - binary : binary input data of BER-TLV length field - Returns: - Tuple of (tag:dict, len:int, remainder:bytes) - """ - (tagdict, remainder) = bertlv_parse_tag(binary) - (length, remainder) = bertlv_parse_len(remainder) - value = remainder[:length] - remainder = remainder[length:] - return (tagdict, length, value, remainder) +def bertlv_encode_len(length: int) -> bytes: + """Encode a single Length value according to ITU-T X.690 8.1.3; + only the definite form is supported here. + Args: + length : length value to be encoded + Returns: + binary output data of BER-TLV length field + """ + if length < 0x80: + return length.to_bytes(1, 'big') + elif length <= 0xff: + return b'\x81' + length.to_bytes(1, 'big') + elif length <= 0xffff: + return b'\x82' + length.to_bytes(2, 'big') + elif length <= 0xffffff: + return b'\x83' + length.to_bytes(3, 'big') + elif length <= 0xffffffff: + return b'\x84' + length.to_bytes(4, 'big') + else: + raise ValueError("Length > 32bits not supported") + + +def bertlv_parse_one(binary: bytes) -> Tuple[dict, int, bytes, bytes]: + """Parse a single TLV IE at the start of the given binary data. + Args: + binary : binary input data of BER-TLV length field + Returns: + Tuple of (tag:dict, len:int, remainder:bytes) + """ + (tagdict, remainder) = bertlv_parse_tag(binary) + (length, remainder) = bertlv_parse_len(remainder) + value = remainder[:length] + remainder = remainder[length:] + return (tagdict, length, value, remainder) # IMSI encoded format: @@ -341,828 +362,889 @@ 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 # even length IMSI only uses half of the last byte. -def enc_imsi(imsi:str): - """Converts a string IMSI into the encoded value of the EF""" - l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator - oe = len(imsi) & 1 # Odd (1) / Even (0) - ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15))) - return ei +def enc_imsi(imsi: str): + """Converts a string IMSI into the encoded value of the EF""" + l = half_round_up( + len(imsi) + 1) # Required bytes - include space for odd/even indicator + oe = len(imsi) & 1 # Odd (1) / Even (0) + ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe << 3) | 1, rpad(imsi, 15))) + return ei -def dec_imsi(ef:Hexstr) -> Optional[str]: - """Converts an EF value to the IMSI string representation""" - if len(ef) < 4: - return None - l = int(ef[0:2], 16) * 2 # Length of the IMSI string - l = l - 1 # Encoded length byte includes oe nibble - swapped = swap_nibbles(ef[2:]).rstrip('f') - if len(swapped) < 1: - return None - oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0) - if not oe: - # if even, only half of last byte was used - l = l-1 - if l != len(swapped) - 1: - return None - imsi = swapped[1:] - return imsi -def dec_iccid(ef:Hexstr) -> str: - return swap_nibbles(ef).strip('f') +def dec_imsi(ef: Hexstr) -> Optional[str]: + """Converts an EF value to the IMSI string representation""" + if len(ef) < 4: + return None + l = int(ef[0:2], 16) * 2 # Length of the IMSI string + l = l - 1 # Encoded length byte includes oe nibble + swapped = swap_nibbles(ef[2:]).rstrip('f') + if len(swapped) < 1: + return None + oe = (int(swapped[0]) >> 3) & 1 # Odd (1) / Even (0) + if not oe: + # if even, only half of last byte was used + l = l-1 + if l != len(swapped) - 1: + return None + imsi = swapped[1:] + return imsi -def enc_iccid(iccid:str) -> Hexstr: - return swap_nibbles(rpad(iccid, 20)) -def enc_plmn(mcc:Hexstr, mnc:Hexstr) -> Hexstr: - """Converts integer MCC/MNC into 3 bytes for EF""" +def dec_iccid(ef: Hexstr) -> str: + return swap_nibbles(ef).strip('f') - # Make sure there are no excess whitespaces in the input - # parameters - mcc = mcc.strip() - mnc = mnc.strip() - # Make sure that MCC/MNC are correctly padded with leading - # zeros or 'F', depending on the length. - if len(mnc) == 0: - mnc = "FFF" - elif len(mnc) == 1: - mnc = "F0" + mnc - elif len(mnc) == 2: - mnc += "F" +def enc_iccid(iccid: str) -> Hexstr: + return swap_nibbles(rpad(iccid, 20)) - if len(mcc) == 0: - mcc = "FFF" - elif len(mcc) == 1: - mcc = "00" + mcc - elif len(mcc) == 2: - mcc = "0" + mcc - return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0]) +def enc_plmn(mcc: Hexstr, mnc: Hexstr) -> Hexstr: + """Converts integer MCC/MNC into 3 bytes for EF""" + + # Make sure there are no excess whitespaces in the input + # parameters + mcc = mcc.strip() + mnc = mnc.strip() + + # Make sure that MCC/MNC are correctly padded with leading + # zeros or 'F', depending on the length. + if len(mnc) == 0: + mnc = "FFF" + elif len(mnc) == 1: + mnc = "F0" + mnc + elif len(mnc) == 2: + mnc += "F" + + if len(mcc) == 0: + mcc = "FFF" + elif len(mcc) == 1: + mcc = "00" + mcc + elif len(mcc) == 2: + mcc = "0" + mcc + + return (mcc[1] + mcc[0]) + (mnc[2] + mcc[2]) + (mnc[1] + mnc[0]) + + +def dec_plmn(threehexbytes: Hexstr) -> dict: + res = {'mcc': "0", 'mnc': "0"} + dec_mcc_from_plmn_str(threehexbytes) + res['mcc'] = dec_mcc_from_plmn_str(threehexbytes) + res['mnc'] = dec_mnc_from_plmn_str(threehexbytes) + return res -def dec_plmn(threehexbytes:Hexstr) -> dict: - res = {'mcc': "0", 'mnc': "0" } - dec_mcc_from_plmn_str(threehexbytes) - res['mcc'] = dec_mcc_from_plmn_str(threehexbytes) - res['mnc'] = dec_mnc_from_plmn_str(threehexbytes) - return res def dec_spn(ef): - """Obsolete, kept for API compatibility""" - from ts_51_011 import EF_SPN - abstract_data = EF_SPN().decode_hex(ef) - show_in_hplmn = abstract_data['show_in_hplmn'] - hide_in_oplmn = abstract_data['hide_in_oplmn'] - name = abstract_data['spn'] - return (name, show_in_hplmn, hide_in_oplmn) + """Obsolete, kept for API compatibility""" + from ts_51_011 import EF_SPN + abstract_data = EF_SPN().decode_hex(ef) + show_in_hplmn = abstract_data['show_in_hplmn'] + hide_in_oplmn = abstract_data['hide_in_oplmn'] + name = abstract_data['spn'] + return (name, show_in_hplmn, hide_in_oplmn) + + +def enc_spn(name: str, show_in_hplmn=False, hide_in_oplmn=False): + """Obsolete, kept for API compatibility""" + from ts_51_011 import EF_SPN + abstract_data = { + 'hide_in_oplmn': hide_in_oplmn, + 'show_in_hplmn': show_in_hplmn, + 'spn': name, + } + return EF_SPN().encode_hex(abstract_data) -def enc_spn(name:str, show_in_hplmn=False, hide_in_oplmn=False): - """Obsolete, kept for API compatibility""" - from ts_51_011 import EF_SPN - abstract_data = { - 'hide_in_oplmn' : hide_in_oplmn, - 'show_in_hplmn' : show_in_hplmn, - 'spn' : name, - } - return EF_SPN().encode_hex(abstract_data) def hexstr_to_Nbytearr(s, nbytes): - return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2)) ] + return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))] # Accepts hex string representing three bytes -def dec_mcc_from_plmn(plmn:Hexstr) -> int: - ia = h2i(plmn) - digit1 = ia[0] & 0x0F # 1st byte, LSB - digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB - digit3 = ia[1] & 0x0F # 2nd byte, LSB - if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF: - return 0xFFF # 4095 - return derive_mcc(digit1, digit2, digit3) -def dec_mcc_from_plmn_str(plmn:Hexstr) -> str: - digit1 = plmn[1] # 1st byte, LSB - digit2 = plmn[0] # 1st byte, MSB - digit3 = plmn[3] # 2nd byte, LSB - res = digit1 + digit2 + digit3 - return res.upper().strip("F") -def dec_mnc_from_plmn(plmn:Hexstr) -> int: - ia = h2i(plmn) - digit1 = ia[2] & 0x0F # 3rd byte, LSB - digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB - digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB - if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF: - return 0xFFF # 4095 - return derive_mnc(digit1, digit2, digit3) +def dec_mcc_from_plmn(plmn: Hexstr) -> int: + ia = h2i(plmn) + digit1 = ia[0] & 0x0F # 1st byte, LSB + digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB + digit3 = ia[1] & 0x0F # 2nd byte, LSB + if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF: + return 0xFFF # 4095 + return derive_mcc(digit1, digit2, digit3) -def dec_mnc_from_plmn_str(plmn:Hexstr) -> str: - digit1 = plmn[5] # 3rd byte, LSB - digit2 = plmn[4] # 3rd byte, MSB - digit3 = plmn[2] # 2nd byte, MSB - res = digit1 + digit2 + digit3 - return res.upper().strip("F") -def dec_act(twohexbytes:Hexstr) -> List[str]: - act_list = [ - {'bit': 15, 'name': "UTRAN"}, - {'bit': 14, 'name': "E-UTRAN"}, - {'bit': 11, 'name': "NG-RAN"}, - {'bit': 7, 'name': "GSM"}, - {'bit': 6, 'name': "GSM COMPACT"}, - {'bit': 5, 'name': "cdma2000 HRPD"}, - {'bit': 4, 'name': "cdma2000 1xRTT"}, - ] - ia = h2i(twohexbytes) - u16t = (ia[0] << 8)|ia[1] - sel = [] - for a in act_list: - if u16t & (1 << a['bit']): - if a['name'] == "E-UTRAN": - # The Access technology identifier of E-UTRAN - # allows a more detailed specification: - if u16t & (1 << 13) and u16t & (1 << 12): - sel.append("E-UTRAN WB-S1") - sel.append("E-UTRAN NB-S1") - elif u16t & (1 << 13): - sel.append("E-UTRAN WB-S1") - elif u16t & (1 << 12): - sel.append("E-UTRAN NB-S1") - else: - sel.append("E-UTRAN") - else: - sel.append(a['name']) - return sel +def dec_mcc_from_plmn_str(plmn: Hexstr) -> str: + digit1 = plmn[1] # 1st byte, LSB + digit2 = plmn[0] # 1st byte, MSB + digit3 = plmn[3] # 2nd byte, LSB + res = digit1 + digit2 + digit3 + return res.upper().strip("F") + + +def dec_mnc_from_plmn(plmn: Hexstr) -> int: + ia = h2i(plmn) + digit1 = ia[2] & 0x0F # 3rd byte, LSB + digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB + digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB + if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF: + return 0xFFF # 4095 + return derive_mnc(digit1, digit2, digit3) + + +def dec_mnc_from_plmn_str(plmn: Hexstr) -> str: + digit1 = plmn[5] # 3rd byte, LSB + digit2 = plmn[4] # 3rd byte, MSB + digit3 = plmn[2] # 2nd byte, MSB + res = digit1 + digit2 + digit3 + return res.upper().strip("F") + + +def dec_act(twohexbytes: Hexstr) -> List[str]: + act_list = [ + {'bit': 15, 'name': "UTRAN"}, + {'bit': 14, 'name': "E-UTRAN"}, + {'bit': 11, 'name': "NG-RAN"}, + {'bit': 7, 'name': "GSM"}, + {'bit': 6, 'name': "GSM COMPACT"}, + {'bit': 5, 'name': "cdma2000 HRPD"}, + {'bit': 4, 'name': "cdma2000 1xRTT"}, + ] + ia = h2i(twohexbytes) + u16t = (ia[0] << 8) | ia[1] + sel = [] + for a in act_list: + if u16t & (1 << a['bit']): + if a['name'] == "E-UTRAN": + # The Access technology identifier of E-UTRAN + # allows a more detailed specification: + if u16t & (1 << 13) and u16t & (1 << 12): + sel.append("E-UTRAN WB-S1") + sel.append("E-UTRAN NB-S1") + elif u16t & (1 << 13): + sel.append("E-UTRAN WB-S1") + elif u16t & (1 << 12): + sel.append("E-UTRAN NB-S1") + else: + sel.append("E-UTRAN") + else: + sel.append(a['name']) + return sel + + +def dec_xplmn_w_act(fivehexbytes: Hexstr) -> Dict[str, Any]: + res = {'mcc': "0", 'mnc': "0", 'act': []} + plmn_chars = 6 + act_chars = 4 + # first three bytes (six ascii hex chars) + plmn_str = fivehexbytes[:plmn_chars] + # two bytes after first three bytes + act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] + res['mcc'] = dec_mcc_from_plmn_str(plmn_str) + res['mnc'] = dec_mnc_from_plmn_str(plmn_str) + res['act'] = dec_act(act_str) + return res -def dec_xplmn_w_act(fivehexbytes:Hexstr) -> Dict[str,Any]: - res = {'mcc': "0", 'mnc': "0", 'act': []} - plmn_chars = 6 - act_chars = 4 - plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars) - act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes - res['mcc'] = dec_mcc_from_plmn_str(plmn_str) - res['mnc'] = dec_mnc_from_plmn_str(plmn_str) - res['act'] = dec_act(act_str) - return res def format_xplmn_w_act(hexstr): - s = "" - for rec_data in hexstr_to_Nbytearr(hexstr, 5): - rec_info = dec_xplmn_w_act(rec_data) - if rec_info['mcc'] == "" and rec_info['mnc'] == "": - rec_str = "unused" - else: - rec_str = "MCC: %s MNC: %s AcT: %s" % (rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act'])) - s += "\t%s # %s\n" % (rec_data, rec_str) - return s + s = "" + for rec_data in hexstr_to_Nbytearr(hexstr, 5): + rec_info = dec_xplmn_w_act(rec_data) + if rec_info['mcc'] == "" and rec_info['mnc'] == "": + rec_str = "unused" + else: + rec_str = "MCC: %s MNC: %s AcT: %s" % ( + rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act'])) + s += "\t%s # %s\n" % (rec_data, rec_str) + return s + def dec_loci(hexstr): - res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0} - res['tmsi'] = hexstr[:8] - res['mcc'] = dec_mcc_from_plmn(hexstr[8:14]) - res['mnc'] = dec_mnc_from_plmn(hexstr[8:14]) - res['lac'] = hexstr[14:18] - res['status'] = h2i(hexstr[20:22]) - return res + res = {'tmsi': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'status': 0} + res['tmsi'] = hexstr[:8] + res['mcc'] = dec_mcc_from_plmn(hexstr[8:14]) + res['mnc'] = dec_mnc_from_plmn(hexstr[8:14]) + res['lac'] = hexstr[14:18] + res['status'] = h2i(hexstr[20:22]) + return res + def dec_psloci(hexstr): - res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, 'mnc': 0, 'lac': '', 'rac': '', 'status': 0} - res['p-tmsi'] = hexstr[:8] - res['p-tmsi-sig'] = hexstr[8:14] - res['mcc'] = dec_mcc_from_plmn(hexstr[14:20]) - res['mnc'] = dec_mnc_from_plmn(hexstr[14:20]) - res['lac'] = hexstr[20:24] - res['rac'] = hexstr[24:26] - res['status'] = h2i(hexstr[26:28]) - return res + res = {'p-tmsi': '', 'p-tmsi-sig': '', 'mcc': 0, + 'mnc': 0, 'lac': '', 'rac': '', 'status': 0} + res['p-tmsi'] = hexstr[:8] + res['p-tmsi-sig'] = hexstr[8:14] + res['mcc'] = dec_mcc_from_plmn(hexstr[14:20]) + res['mnc'] = dec_mnc_from_plmn(hexstr[14:20]) + res['lac'] = hexstr[20:24] + res['rac'] = hexstr[24:26] + res['status'] = h2i(hexstr[26:28]) + return res + def dec_epsloci(hexstr): - res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0} - res['guti'] = hexstr[:24] - res['tai'] = hexstr[24:34] - res['mcc'] = dec_mcc_from_plmn(hexstr[24:30]) - res['mnc'] = dec_mnc_from_plmn(hexstr[24:30]) - res['tac'] = hexstr[30:34] - res['status'] = h2i(hexstr[34:36]) - return res + res = {'guti': '', 'mcc': 0, 'mnc': 0, 'tac': '', 'status': 0} + res['guti'] = hexstr[:24] + res['tai'] = hexstr[24:34] + res['mcc'] = dec_mcc_from_plmn(hexstr[24:30]) + res['mnc'] = dec_mnc_from_plmn(hexstr[24:30]) + res['tac'] = hexstr[30:34] + res['status'] = h2i(hexstr[34:36]) + return res -def dec_xplmn(threehexbytes:Hexstr) -> dict: - res = {'mcc': 0, 'mnc': 0, 'act': []} - plmn_chars = 6 - plmn_str = threehexbytes[:plmn_chars] # first three bytes (six ascii hex chars) - res['mcc'] = dec_mcc_from_plmn(plmn_str) - res['mnc'] = dec_mnc_from_plmn(plmn_str) - return res -def format_xplmn(hexstr:Hexstr) -> str: - s = "" - for rec_data in hexstr_to_Nbytearr(hexstr, 3): - rec_info = dec_xplmn(rec_data) - if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF: - rec_str = "unused" - else: - rec_str = "MCC: %03d MNC: %03d" % (rec_info['mcc'], rec_info['mnc']) - s += "\t%s # %s\n" % (rec_data, rec_str) - return s +def dec_xplmn(threehexbytes: Hexstr) -> dict: + res = {'mcc': 0, 'mnc': 0, 'act': []} + plmn_chars = 6 + # first three bytes (six ascii hex chars) + plmn_str = threehexbytes[:plmn_chars] + res['mcc'] = dec_mcc_from_plmn(plmn_str) + res['mnc'] = dec_mnc_from_plmn(plmn_str) + return res -def derive_milenage_opc(ki_hex:Hexstr, op_hex:Hexstr) -> Hexstr: - """ - Run the milenage algorithm to calculate OPC from Ki and OP - """ - from Crypto.Cipher import AES - # pylint: disable=no-name-in-module - from Crypto.Util.strxor import strxor - from pySim.utils import b2h - # We pass in hex string and now need to work on bytes - ki_bytes = bytes(h2b(ki_hex)) - op_bytes = bytes(h2b(op_hex)) - aes = AES.new(ki_bytes, AES.MODE_ECB) - opc_bytes = aes.encrypt(op_bytes) - return b2h(strxor(opc_bytes, op_bytes)) +def format_xplmn(hexstr: Hexstr) -> str: + s = "" + for rec_data in hexstr_to_Nbytearr(hexstr, 3): + rec_info = dec_xplmn(rec_data) + if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF: + rec_str = "unused" + else: + rec_str = "MCC: %03d MNC: %03d" % ( + rec_info['mcc'], rec_info['mnc']) + s += "\t%s # %s\n" % (rec_data, rec_str) + return s + + +def derive_milenage_opc(ki_hex: Hexstr, op_hex: Hexstr) -> Hexstr: + """ + Run the milenage algorithm to calculate OPC from Ki and OP + """ + from Crypto.Cipher import AES + # pylint: disable=no-name-in-module + from Crypto.Util.strxor import strxor + from pySim.utils import b2h + + # We pass in hex string and now need to work on bytes + ki_bytes = bytes(h2b(ki_hex)) + op_bytes = bytes(h2b(op_hex)) + aes = AES.new(ki_bytes, AES.MODE_ECB) + opc_bytes = aes.encrypt(op_bytes) + return b2h(strxor(opc_bytes, op_bytes)) + def calculate_luhn(cc) -> int: - """ - Calculate Luhn checksum used in e.g. ICCID and IMEI - """ - num = list(map(int, str(cc))) - check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10 - return 0 if check_digit == 10 else check_digit + """ + Calculate Luhn checksum used in e.g. ICCID and IMEI + """ + num = list(map(int, str(cc))) + check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) + for d in num[::-2]]) % 10 + return 0 if check_digit == 10 else check_digit -def mcc_from_imsi(imsi:str) -> Optional[str]: - """ - Derive the MCC (Mobile Country Code) from the first three digits of an IMSI - """ - if imsi == None: - return None - if len(imsi) > 3: - return imsi[:3] - else: - return None +def mcc_from_imsi(imsi: str) -> Optional[str]: + """ + Derive the MCC (Mobile Country Code) from the first three digits of an IMSI + """ + if imsi == None: + return None -def mnc_from_imsi(imsi:str, long:bool=False) -> Optional[str]: - """ - Derive the MNC (Mobile Country Code) from the 4th to 6th digit of an IMSI - """ - if imsi == None: - return None + if len(imsi) > 3: + return imsi[:3] + else: + return None - if len(imsi) > 3: - if long: - return imsi[3:6] - else: - return imsi[3:5] - else: - return None -def derive_mcc(digit1:int, digit2:int, digit3:int) -> int: - """ - Derive decimal representation of the MCC (Mobile Country Code) - from three given digits. - """ +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 + """ + if imsi == None: + return None - mcc = 0 + if len(imsi) > 3: + if long: + return imsi[3:6] + else: + return imsi[3:5] + else: + return None - if digit1 != 0x0f: - mcc += digit1 * 100 - if digit2 != 0x0f: - mcc += digit2 * 10 - if digit3 != 0x0f: - mcc += digit3 - return mcc +def derive_mcc(digit1: int, digit2: int, digit3: int) -> int: + """ + Derive decimal representation of the MCC (Mobile Country Code) + from three given digits. + """ -def derive_mnc(digit1:int, digit2:int, digit3:int=0x0f) -> int: - """ - Derive decimal representation of the MNC (Mobile Network Code) - from two or (optionally) three given digits. - """ + mcc = 0 - mnc = 0 + if digit1 != 0x0f: + mcc += digit1 * 100 + if digit2 != 0x0f: + mcc += digit2 * 10 + if digit3 != 0x0f: + mcc += digit3 - # 3-rd digit is optional for the MNC. If present - # the algorythm is the same as for the MCC. - if digit3 != 0x0f: - return derive_mcc(digit1, digit2, digit3) + return mcc - if digit1 != 0x0f: - mnc += digit1 * 10 - if digit2 != 0x0f: - mnc += digit2 - return mnc +def derive_mnc(digit1: int, digit2: int, digit3: int = 0x0f) -> int: + """ + Derive decimal representation of the MNC (Mobile Network Code) + from two or (optionally) three given digits. + """ -def dec_msisdn(ef_msisdn:Hexstr) -> Optional[Tuple[int,int,Optional[str]]]: - """ - Decode MSISDN from EF.MSISDN or EF.ADN (same structure). - See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. - """ + mnc = 0 - # Convert from str to (kind of) 'bytes' - ef_msisdn = h2b(ef_msisdn) + # 3-rd digit is optional for the MNC. If present + # the algorythm is the same as for the MCC. + if digit3 != 0x0f: + return derive_mcc(digit1, digit2, digit3) - # Make sure mandatory fields are present - if len(ef_msisdn) < 14: - raise ValueError("EF.MSISDN is too short") + if digit1 != 0x0f: + mnc += digit1 * 10 + if digit2 != 0x0f: + mnc += digit2 - # Skip optional Alpha Identifier - xlen = len(ef_msisdn) - 14 - msisdn_lhv = ef_msisdn[xlen:] + return mnc - # Parse the length (in bytes) of the BCD encoded number - bcd_len = msisdn_lhv[0] - # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI - if bcd_len == 0xff: - return None - elif bcd_len > 11 or bcd_len < 1: - raise ValueError("Length of MSISDN (%d bytes) is out of range" % bcd_len) - # Parse ToN / NPI - ton = (msisdn_lhv[1] >> 4) & 0x07 - npi = msisdn_lhv[1] & 0x0f - bcd_len -= 1 +def dec_msisdn(ef_msisdn: Hexstr) -> Optional[Tuple[int, int, Optional[str]]]: + """ + Decode MSISDN from EF.MSISDN or EF.ADN (same structure). + See 3GPP TS 31.102, section 4.2.26 and 4.4.2.3. + """ - # No MSISDN? - if not bcd_len: - return (npi, ton, None) + # Convert from str to (kind of) 'bytes' + ef_msisdn = h2b(ef_msisdn) - msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f') - # International number 10.5.118/3GPP TS 24.008 - if ton == 0x01: - msisdn = '+' + msisdn + # Make sure mandatory fields are present + if len(ef_msisdn) < 14: + raise ValueError("EF.MSISDN is too short") - return (npi, ton, msisdn) + # Skip optional Alpha Identifier + xlen = len(ef_msisdn) - 14 + msisdn_lhv = ef_msisdn[xlen:] -def enc_msisdn(msisdn:str, npi:int=0x01, ton:int=0x03) -> Hexstr: - """ - 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 - will not contain the optional Alpha Identifier at the beginning.) + # Parse the length (in bytes) of the BCD encoded number + bcd_len = msisdn_lhv[0] + # BCD length = length of dial num (max. 10 bytes) + 1 byte ToN and NPI + if bcd_len == 0xff: + return None + elif bcd_len > 11 or bcd_len < 1: + raise ValueError( + "Length of MSISDN (%d bytes) is out of range" % bcd_len) - Default NPI / ToN values: - - NPI: ISDN / telephony numbering plan (E.164 / E.163), - - ToN: network specific or international number (if starts with '+'). - """ + # Parse ToN / NPI + ton = (msisdn_lhv[1] >> 4) & 0x07 + npi = msisdn_lhv[1] & 0x0f + bcd_len -= 1 - # If no MSISDN is supplied then encode the file contents as all "ff" - if msisdn == "" or msisdn == "+": - return "ff" * 14 + # No MSISDN? + if not bcd_len: + return (npi, ton, None) - # Leading '+' indicates International Number - if msisdn[0] == '+': - msisdn = msisdn[1:] - ton = 0x01 + msisdn = swap_nibbles(b2h(msisdn_lhv[2:][:bcd_len])).rstrip('f') + # International number 10.5.118/3GPP TS 24.008 + if ton == 0x01: + msisdn = '+' + msisdn - # An MSISDN must not exceed 20 digits - if len(msisdn) > 20: - raise ValueError("msisdn must not be longer than 20 digits") + return (npi, ton, msisdn) - # Append 'f' padding if number of digits is odd - if len(msisdn) % 2 > 0: - msisdn += 'f' - # BCD length also includes NPI/ToN header - bcd_len = len(msisdn) // 2 + 1 - npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80 - bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets +def enc_msisdn(msisdn: str, npi: int = 0x01, ton: int = 0x03) -> Hexstr: + """ + 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 + will not contain the optional Alpha Identifier at the beginning.) - return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2) + Default NPI / ToN values: + - NPI: ISDN / telephony numbering plan (E.164 / E.163), + - ToN: network specific or international number (if starts with '+'). + """ + + # If no MSISDN is supplied then encode the file contents as all "ff" + if msisdn == "" or msisdn == "+": + return "ff" * 14 + + # Leading '+' indicates International Number + if msisdn[0] == '+': + msisdn = msisdn[1:] + ton = 0x01 + + # An MSISDN must not exceed 20 digits + if len(msisdn) > 20: + raise ValueError("msisdn must not be longer than 20 digits") + + # Append 'f' padding if number of digits is odd + if len(msisdn) % 2 > 0: + msisdn += 'f' + + # BCD length also includes NPI/ToN header + bcd_len = len(msisdn) // 2 + 1 + npi_ton = (npi & 0x0f) | ((ton & 0x07) << 4) | 0x80 + bcd = rpad(swap_nibbles(msisdn), 10 * 2) # pad to 10 octets + + return ('%02x' % bcd_len) + ('%02x' % npi_ton) + bcd + ("ff" * 2) def dec_st(st, table="sim") -> str: - """ - Parses the EF S/U/IST and prints the list of available services in EF S/U/IST - """ + """ + Parses the EF S/U/IST and prints the list of available services in EF S/U/IST + """ - if table == "isim": - from pySim.ts_31_103 import EF_IST_map - lookup_map = EF_IST_map - elif table == "usim": - from pySim.ts_31_102 import EF_UST_map - lookup_map = EF_UST_map - else: - from pySim.ts_51_011 import EF_SST_map - lookup_map = EF_SST_map + if table == "isim": + from pySim.ts_31_103 import EF_IST_map + lookup_map = EF_IST_map + elif table == "usim": + from pySim.ts_31_102 import EF_UST_map + lookup_map = EF_UST_map + else: + from pySim.ts_51_011 import 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 = "" + # Get each byte and check for available services + for i in range(0, len(st_bytes)): + # Byte i contains info about Services num (8i+1) to num (8i+8) + byte = int(st_bytes[i], 16) + # Services in each byte are in order MSB to LSB + # MSB - Service (8i+8) + # LSB - Service (8i+1) + for j in range(1, 9): + if byte & 0x01 == 0x01 and ((8*i) + j in lookup_map): + # Byte X contains info about Services num (8X-7) to num (8X) + # bit = 1: service available + # bit = 0: service not available + avail_st += '\tService %d - %s\n' % ( + (8*i) + j, lookup_map[(8*i) + j]) + byte = byte >> 1 + return avail_st - avail_st = "" - # Get each byte and check for available services - for i in range(0, len(st_bytes)): - # Byte i contains info about Services num (8i+1) to num (8i+8) - byte = int(st_bytes[i], 16) - # Services in each byte are in order MSB to LSB - # MSB - Service (8i+8) - # LSB - Service (8i+1) - for j in range(1, 9): - if byte&0x01 == 0x01 and ((8*i) + j in lookup_map): - # Byte X contains info about Services num (8X-7) to num (8X) - # bit = 1: service available - # bit = 0: service not available - avail_st += '\tService %d - %s\n' % ((8*i) + j, lookup_map[(8*i) + j]) - byte = byte >> 1 - return avail_st 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]) + + parses first TLV format record in a list of bytelist + returns a 3-Tuple: Tag, Length, Value + Value is a list of bytes + parsing of length is ETSI'style 101.220 + ''' + Tag = bytelist[0] + if bytelist[1] == 0xFF: + Len = bytelist[2]*256 + bytelist[3] + Val = bytelist[4:4+Len] + else: + Len = bytelist[1] + Val = bytelist[2:2+Len] + return (Tag, Len, Val) - parses first TLV format record in a list of bytelist - returns a 3-Tuple: Tag, Length, Value - Value is a list of bytes - parsing of length is ETSI'style 101.220 - ''' - Tag = bytelist[0] - if bytelist[1] == 0xFF: - Len = bytelist[2]*256 + bytelist[3] - Val = bytelist[4:4+Len] - else: - Len = bytelist[1] - Val = bytelist[2:2+Len] - return (Tag, Len, Val) def TLV_parser(bytelist): - ''' - TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...] + ''' + TLV_parser([0xAA, ..., 0xFF]) -> [(T, L, [V]), (T, L, [V]), ...] + + loops on the input list of bytes with the "first_TLV_parser()" function + returns a list of 3-Tuples + ''' + ret = [] + while len(bytelist) > 0: + T, L, V = first_TLV_parser(bytelist) + if T == 0xFF: + # padding bytes + break + ret.append((T, L, V)) + # need to manage length of L + if L > 0xFE: + bytelist = bytelist[L+4:] + else: + bytelist = bytelist[L+2:] + return ret - loops on the input list of bytes with the "first_TLV_parser()" function - returns a list of 3-Tuples - ''' - ret = [] - while len(bytelist) > 0: - T, L, V = first_TLV_parser(bytelist) - if T == 0xFF: - # padding bytes - break - ret.append( (T, L, V) ) - # need to manage length of L - if L > 0xFE: - bytelist = bytelist[ L+4 : ] - else: - bytelist = bytelist[ L+2 : ] - return ret def enc_st(st, service, state=1): - """ - Encodes the EF S/U/IST/EST and returns the updated Service Table + """ + Encodes the EF S/U/IST/EST and returns the updated Service Table - Parameters: - st - Current value of SIM/USIM/ISIM Service Table - service - Service Number to encode as activated/de-activated - state - 1 mean activate, 0 means de-activate + Parameters: + st - Current value of SIM/USIM/ISIM Service Table + service - Service Number to encode as activated/de-activated + state - 1 mean activate, 0 means de-activate - Returns: - s - Modified value of SIM/USIM/ISIM Service Table + Returns: + s - Modified value of SIM/USIM/ISIM Service Table - Default values: - - state: 1 - Sets the particular Service bit to 1 - """ - st_bytes = [st[i:i+2] for i in range(0, len(st), 2) ] + Default values: + - state: 1 - Sets the particular Service bit to 1 + """ + st_bytes = [st[i:i+2] for i in range(0, len(st), 2)] - s = "" - # Check whether the requested service is present in each byte - for i in range(0, len(st_bytes)): - # Byte i contains info about Services num (8i+1) to num (8i+8) - if service in range((8*i) + 1, (8*i) + 9): - byte = int(st_bytes[i], 16) - # Services in each byte are in order MSB to LSB - # MSB - Service (8i+8) - # LSB - Service (8i+1) - mod_byte = 0x00 - # Copy bit by bit contents of byte to mod_byte with modified bit - # for requested service - for j in range(1, 9): - mod_byte = mod_byte >> 1 - if service == (8*i) + j: - mod_byte = state == 1 and mod_byte|0x80 or mod_byte&0x7f - else: - mod_byte = byte&0x01 == 0x01 and mod_byte|0x80 or mod_byte&0x7f - byte = byte >> 1 + s = "" + # Check whether the requested service is present in each byte + for i in range(0, len(st_bytes)): + # Byte i contains info about Services num (8i+1) to num (8i+8) + if service in range((8*i) + 1, (8*i) + 9): + byte = int(st_bytes[i], 16) + # Services in each byte are in order MSB to LSB + # MSB - Service (8i+8) + # LSB - Service (8i+1) + mod_byte = 0x00 + # Copy bit by bit contents of byte to mod_byte with modified bit + # for requested service + for j in range(1, 9): + mod_byte = mod_byte >> 1 + if service == (8*i) + j: + mod_byte = state == 1 and mod_byte | 0x80 or mod_byte & 0x7f + else: + mod_byte = byte & 0x01 == 0x01 and mod_byte | 0x80 or mod_byte & 0x7f + byte = byte >> 1 - s += ('%02x' % (mod_byte)) - else: - s += st_bytes[i] + s += ('%02x' % (mod_byte)) + else: + s += st_bytes[i] + + return s - return s def dec_addr_tlv(hexstr): - """ - Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm. - See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104. - """ + """ + Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm. + See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104. + """ - # Convert from hex str to int bytes list - addr_tlv_bytes = h2i(hexstr) + # Convert from hex str to int bytes list + addr_tlv_bytes = h2i(hexstr) - # Get list of tuples containing parsed TLVs - tlvs = TLV_parser(addr_tlv_bytes) + # Get list of tuples containing parsed TLVs + tlvs = TLV_parser(addr_tlv_bytes) - for tlv in tlvs: - # tlv = (T, L, [V]) - # T = Tag - # L = Length - # [V] = List of value + for tlv in tlvs: + # tlv = (T, L, [V]) + # T = Tag + # L = Length + # [V] = List of value - # Invalid Tag value scenario - if tlv[0] != 0x80: - continue + # Invalid Tag value scenario + if tlv[0] != 0x80: + continue - # Empty field - Zero length - if tlv[1] == 0: - continue + # Empty field - Zero length + if tlv[1] == 0: + continue - # First byte in the value has the address type - addr_type = tlv[2][0] - # TODO: Support parsing of IPv6 - # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved) - if addr_type == 0x00: #FQDN - # Skip address tye byte i.e. first byte in value list - content = tlv[2][1:] - return (i2s(content), '00') + # First byte in the value has the address type + addr_type = tlv[2][0] + # TODO: Support parsing of IPv6 + # Address Type: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), other (Reserved) + if addr_type == 0x00: # FQDN + # Skip address tye byte i.e. first byte in value list + content = tlv[2][1:] + return (i2s(content), '00') - elif addr_type == 0x01: #IPv4 - # 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 - ipv4 = tlv[2][2:] - content = '.'.join(str(x) for x in ipv4) - return (content, '01') - else: - raise ValueError("Invalid address type") + elif addr_type == 0x01: # IPv4 + # 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 + ipv4 = tlv[2][2:] + content = '.'.join(str(x) for x in ipv4) + return (content, '01') + else: + raise ValueError("Invalid address type") + + return (None, None) - return (None, None) def enc_addr_tlv(addr, addr_type='00'): - """ - Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm. - See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104. + """ + Encode address TLV object used in EF.P-CSCF Address, EF.ePDGId and EF.ePDGIdEm. + See 3GPP TS 31.102 version 13.4.0 Release 13, section 4.2.8, 4.2.102 and 4.2.104. - Default values: - - addr_type: 00 - FQDN format of Address - """ + Default values: + - addr_type: 00 - FQDN format of Address + """ - s = "" + s = "" - # TODO: Encoding of IPv6 address - if addr_type == '00': #FQDN - hex_str = s2h(addr) - s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str - elif addr_type == '01': #IPv4 - ipv4_list = addr.split('.') - ipv4_str = "" - for i in ipv4_list: - ipv4_str += ('%02x' % (int(i))) + # TODO: Encoding of IPv6 address + if addr_type == '00': # FQDN + hex_str = s2h(addr) + s += '80' + ('%02x' % ((len(hex_str)//2)+1)) + '00' + hex_str + elif addr_type == '01': # IPv4 + ipv4_list = addr.split('.') + ipv4_str = "" + for i in ipv4_list: + ipv4_str += ('%02x' % (int(i))) - # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used - # IPv4 Address is in octet 5 to octet 8 of the TLV data object - s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str + # Unused bytes shall be set to 'ff'. i.e 4th Octet after Address Type is not used + # IPv4 Address is in octet 5 to octet 8 of the TLV data object + s += '80' + ('%02x' % ((len(ipv4_str)//2)+2)) + '01' + 'ff' + ipv4_str - return s + return s -def is_hex(string:str, minlen:int=2, maxlen:Optional[int]=None) -> bool: - """ - Check if a string is a valid hexstring - """ - # Filter obviously bad strings - if not string: - return False - if len(string) < minlen or minlen < 2: - return False - if len(string) % 2: - return False - if maxlen and len(string) > maxlen: - return False +def is_hex(string: str, minlen: int = 2, maxlen: Optional[int] = None) -> bool: + """ + Check if a string is a valid hexstring + """ - # Try actual encoding to be sure - try: - try_encode = h2b(string) - return True - except: - return False + # Filter obviously bad strings + if not string: + return False + if len(string) < minlen or minlen < 2: + return False + if len(string) % 2: + return False + if maxlen and len(string) > maxlen: + return False -def sanitize_pin_adm(pin_adm, pin_adm_hex = None) -> Hexstr: - """ - The ADM pin can be supplied either in its hexadecimal form or as - ascii string. This function checks the supplied opts parameter and - returns the pin_adm as hex encoded string, regardless in which form - it was originally supplied by the user - """ + # Try actual encoding to be sure + try: + try_encode = h2b(string) + return True + except: + return False - if pin_adm is not None: - if len(pin_adm) <= 8: - pin_adm = ''.join(['%02x'%(ord(x)) for x in pin_adm]) - pin_adm = rpad(pin_adm, 16) - else: - raise ValueError("PIN-ADM needs to be <=8 digits (ascii)") +def sanitize_pin_adm(pin_adm, pin_adm_hex=None) -> Hexstr: + """ + The ADM pin can be supplied either in its hexadecimal form or as + ascii string. This function checks the supplied opts parameter and + returns the pin_adm as hex encoded string, regardless in which form + it was originally supplied by the user + """ - if pin_adm_hex is not None: - if len(pin_adm_hex) == 16: - pin_adm = pin_adm_hex - # Ensure that it's hex-encoded - try: - try_encode = h2b(pin_adm) - except ValueError: - raise ValueError("PIN-ADM needs to be hex encoded using this option") - else: - raise ValueError("PIN-ADM needs to be exactly 16 digits (hex encoded)") + if pin_adm is not None: + if len(pin_adm) <= 8: + pin_adm = ''.join(['%02x' % (ord(x)) for x in pin_adm]) + pin_adm = rpad(pin_adm, 16) + + else: + raise ValueError("PIN-ADM needs to be <=8 digits (ascii)") + + if pin_adm_hex is not None: + if len(pin_adm_hex) == 16: + pin_adm = pin_adm_hex + # Ensure that it's hex-encoded + try: + try_encode = h2b(pin_adm) + except ValueError: + raise ValueError( + "PIN-ADM needs to be hex encoded using this option") + else: + raise ValueError( + "PIN-ADM needs to be exactly 16 digits (hex encoded)") + + return pin_adm - return pin_adm def enc_ePDGSelection(hexstr, mcc, mnc, epdg_priority='0001', epdg_fqdn_format='00'): - """ - Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm. - See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106. + """ + Encode ePDGSelection so it can be stored at EF.ePDGSelection or EF.ePDGSelectionEm. + See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106. - Default values: - - epdg_priority: '0001' - 1st Priority - - epdg_fqdn_format: '00' - Operator Identifier FQDN - """ + Default values: + - epdg_priority: '0001' - 1st Priority + - epdg_fqdn_format: '00' - Operator Identifier FQDN + """ + + plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format + # TODO: Handle encoding of Length field for length more than 127 Bytes + content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1 + content = rpad(content, len(hexstr)) + return content - plmn1 = enc_plmn(mcc, mnc) + epdg_priority + epdg_fqdn_format - # TODO: Handle encoding of Length field for length more than 127 Bytes - content = '80' + ('%02x' % (len(plmn1)//2)) + plmn1 - content = rpad(content, len(hexstr)) - return content def dec_ePDGSelection(sixhexbytes): - """ - Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm. - See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106. - """ + """ + Decode ePDGSelection to get EF.ePDGSelection or EF.ePDGSelectionEm. + See 3GPP TS 31.102 version 15.2.0 Release 15, section 4.2.104 and 4.2.106. + """ + + res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''} + plmn_chars = 6 + epdg_priority_chars = 4 + epdg_fqdn_format_chars = 2 + # first three bytes (six ascii hex chars) + plmn_str = sixhexbytes[:plmn_chars] + # two bytes after first three bytes + epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + + epdg_priority_chars] + # 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] + res['mcc'] = dec_mcc_from_plmn(plmn_str) + res['mnc'] = dec_mnc_from_plmn(plmn_str) + res['epdg_priority'] = epdg_priority_str + res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN' + return res - res = {'mcc': 0, 'mnc': 0, 'epdg_priority': 0, 'epdg_fqdn_format': ''} - plmn_chars = 6 - epdg_priority_chars = 4 - epdg_fqdn_format_chars = 2 - # first three bytes (six ascii hex chars) - plmn_str = sixhexbytes[:plmn_chars] - # two bytes after first three bytes - epdg_priority_str = sixhexbytes[plmn_chars:plmn_chars + epdg_priority_chars] - # 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] - res['mcc'] = dec_mcc_from_plmn(plmn_str) - res['mnc'] = dec_mnc_from_plmn(plmn_str) - res['epdg_priority'] = epdg_priority_str - res['epdg_fqdn_format'] = epdg_fqdn_format_str == '00' and 'Operator Identifier FQDN' or 'Location based FQDN' - return res def format_ePDGSelection(hexstr): - ePDGSelection_info_tag_chars = 2 - ePDGSelection_info_tag_str = hexstr[:2] - s = "" - # Minimum length - len_chars = 2 - # TODO: Need to determine length properly - definite length support only - # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104 - # As per spec, length is 5n, n - number of PLMNs - # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte) - # Totalling to 6 Bytes, maybe length should be 6n - len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars] + ePDGSelection_info_tag_chars = 2 + ePDGSelection_info_tag_str = hexstr[:2] + s = "" + # Minimum length + len_chars = 2 + # TODO: Need to determine length properly - definite length support only + # Inconsistency in spec: 3GPP TS 31.102 version 15.2.0 Release 15, 4.2.104 + # As per spec, length is 5n, n - number of PLMNs + # But, each PLMN entry is made of PLMN (3 Bytes) + ePDG Priority (2 Bytes) + ePDG FQDN format (1 Byte) + # Totalling to 6 Bytes, maybe length should be 6n + len_str = hexstr[ePDGSelection_info_tag_chars:ePDGSelection_info_tag_chars+len_chars] - # Not programmed scenario - if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255: - len_chars = 0 - ePDGSelection_info_tag_chars = 0 - if len_str[0] == '8': - # The bits 7 to 1 denotes the number of length octets if length > 127 - if int(len_str[1]) > 0: - # Update number of length octets - len_chars = len_chars * int(len_str[1]) - len_str = hexstr[ePDGSelection_info_tag_chars:len_chars] + # Not programmed scenario + if int(len_str, 16) == 255 or int(ePDGSelection_info_tag_str, 16) == 255: + len_chars = 0 + ePDGSelection_info_tag_chars = 0 + if len_str[0] == '8': + # The bits 7 to 1 denotes the number of length octets if length > 127 + if int(len_str[1]) > 0: + # Update number of length octets + len_chars = len_chars * int(len_str[1]) + len_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 + content_str = rpad(content_str, len(content_str) + + (12 - (len(content_str) % 12))) + for rec_data in hexstr_to_Nbytearr(content_str, 6): + rec_info = dec_ePDGSelection(rec_data) + if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF: + rec_str = "unused" + else: + rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \ + (rec_info['mcc'], rec_info['mnc'], + rec_info['epdg_priority'], rec_info['epdg_fqdn_format']) + s += "\t%s # %s\n" % (rec_data, rec_str) + return s - content_str = hexstr[ePDGSelection_info_tag_chars+len_chars:] - # Right pad to prevent index out of range - multiple of 6 bytes - content_str = rpad(content_str, len(content_str) + (12 - (len(content_str) % 12))) - for rec_data in hexstr_to_Nbytearr(content_str, 6): - rec_info = dec_ePDGSelection(rec_data) - if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF: - rec_str = "unused" - else: - rec_str = "MCC: %03d MNC: %03d ePDG Priority: %s ePDG FQDN format: %s" % \ - (rec_info['mcc'], rec_info['mnc'], rec_info['epdg_priority'], rec_info['epdg_fqdn_format']) - s += "\t%s # %s\n" % (rec_data, rec_str) - return s def get_addr_type(addr): - """ - Validates the given address and returns it's type (FQDN or IPv4 or IPv6) - Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given) + """ + Validates the given address and returns it's type (FQDN or IPv4 or IPv6) + Return: 0x00 (FQDN), 0x01 (IPv4), 0x02 (IPv6), None (Bad address argument given) - TODO: Handle IPv6 - """ + TODO: Handle IPv6 + """ - # Empty address string - if not len(addr): - return None + # Empty address string + if not len(addr): + return None - addr_list = addr.split('.') + addr_list = addr.split('.') - # Check for IPv4/IPv6 - try: - import ipaddress - # Throws ValueError if addr is not correct - ipa = ipaddress.ip_address(addr) + # Check for IPv4/IPv6 + try: + import ipaddress + # Throws ValueError if addr is not correct + ipa = ipaddress.ip_address(addr) - if ipa.version == 4: - return 0x01 - elif ipa.version == 6: - return 0x02 - except Exception as e: - invalid_ipv4 = True - for i in addr_list: - # Invalid IPv4 may qualify for a valid FQDN, so make check here - # e.g. 172.24.15.300 - import re - if not re.match('^[0-9_]+$', i): - invalid_ipv4 = False - break + if ipa.version == 4: + return 0x01 + elif ipa.version == 6: + return 0x02 + except Exception as e: + invalid_ipv4 = True + for i in addr_list: + # Invalid IPv4 may qualify for a valid FQDN, so make check here + # e.g. 172.24.15.300 + import re + if not re.match('^[0-9_]+$', i): + invalid_ipv4 = False + break - if invalid_ipv4: - return None + if invalid_ipv4: + return None - fqdn_flag = True - for i in addr_list: - # Only Alpha-numeric characters and hyphen - RFC 1035 - import re - if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i): - fqdn_flag = False - break + fqdn_flag = True + for i in addr_list: + # Only Alpha-numeric characters and hyphen - RFC 1035 + import re + if not re.match("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)?$", i): + fqdn_flag = False + break - # FQDN - if fqdn_flag: - return 0x00 + # FQDN + if fqdn_flag: + return 0x00 - return None + return None -def sw_match(sw:str, pattern:str) -> bool: - """Match given SW against given pattern.""" - # Create a masked version of the returned status word - sw_lower = sw.lower() - sw_masked = "" - for i in range(0, 4): - if pattern[i] == '?': - sw_masked = sw_masked + '?' - elif pattern[i] == 'x': - sw_masked = sw_masked + 'x' - else: - sw_masked = sw_masked + sw_lower[i] - # Compare the masked version against the pattern - return sw_masked == pattern -def tabulate_str_list(str_list, width:int = 79, hspace:int = 2, lspace:int = 1, - align_left:bool = True) -> str: - """Pretty print a list of strings into a tabulated form. +def sw_match(sw: str, pattern: str) -> bool: + """Match given SW against given pattern.""" + # Create a masked version of the returned status word + sw_lower = sw.lower() + sw_masked = "" + for i in range(0, 4): + if pattern[i] == '?': + sw_masked = sw_masked + '?' + elif pattern[i] == 'x': + sw_masked = sw_masked + 'x' + else: + sw_masked = sw_masked + sw_lower[i] + # Compare the masked version against the pattern + return sw_masked == pattern + + +def tabulate_str_list(str_list, width: int = 79, hspace: int = 2, lspace: int = 1, + align_left: bool = True) -> str: + """Pretty print a list of strings into a tabulated form. + + Args: + width : total width in characters per line + space : horizontal space between cells + lspace : number of spaces before row + align_lef : Align text to the left side + Returns: + multi-line string containing formatted table + """ + if str_list == None: + return "" + if len(str_list) <= 0: + return "" + longest_str = max(str_list, key=len) + cellwith = len(longest_str) + hspace + cols = width // cellwith + rows = (len(str_list) - 1) // cols + 1 + table = [] + for i in iter(range(rows)): + str_list_row = str_list[i::rows] + if (align_left): + format_str_cell = '%%-%ds' + else: + format_str_cell = '%%%ds' + format_str_row = (format_str_cell % cellwith) * len(str_list_row) + format_str_row = (" " * lspace) + format_str_row + table.append(format_str_row % tuple(str_list_row)) + return '\n'.join(table) - Args: - width : total width in characters per line - space : horizontal space between cells - lspace : number of spaces before row - align_lef : Align text to the left side - Returns: - multi-line string containing formatted table - """ - if str_list == None: - return "" - if len(str_list) <= 0: - return "" - longest_str = max(str_list, key=len) - cellwith = len(longest_str) + hspace - cols = width // cellwith - rows = (len(str_list) - 1) // cols + 1 - table = [] - for i in iter(range(rows)): - str_list_row = str_list[i::rows] - if (align_left): - format_str_cell = '%%-%ds' - else: - format_str_cell = '%%%ds' - format_str_row = (format_str_cell % cellwith) * len(str_list_row) - format_str_row = (" " * lspace) + format_str_row - table.append(format_str_row % tuple(str_list_row)) - return '\n'.join(table) def auto_int(x): """Helper function for argparse to accept hexadecimal integers.""" return int(x, 0) + class JsonEncoder(json.JSONEncoder): """Extend the standard library JSONEncoder with support for more types.""" + def default(self, o): if isinstance(o, BytesIO) or isinstance(o, bytes) or isinstance(o, bytearray): return b2h(o) return json.JSONEncoder.default(self, o) + def boxed_heading_str(heading, width=80): - """Generate a string that contains a boxed heading.""" - # Auto-enlarge box if heading exceeds length - if len(heading) > width - 4: - width = len(heading) + 4 - - res = "#" * width - fstr = "\n# %-" + str(width - 4) + "s #\n" - res += fstr % (heading) - res += "#" * width - return res + """Generate a string that contains a boxed heading.""" + # Auto-enlarge box if heading exceeds length + if len(heading) > width - 4: + width = len(heading) + 4 + res = "#" * width + fstr = "\n# %-" + str(width - 4) + "s #\n" + res += fstr % (heading) + res += "#" * width + return res class DataObject(abc.ABC): @@ -1170,7 +1252,8 @@ class DataObject(abc.ABC): simply has any number of different TLVs that may occur in any order at any point, ISO 7816 has the habit of specifying TLV data but with very spcific ordering, or specific choices of tags at specific points in a stream. This class tries to represent this.""" - def __init__(self, name:str, desc:str = None, tag:int = None): + + def __init__(self, name: str, desc: str = None, tag: int = None): """ Args: name: A brief, all-lowercase, underscore separated string identifier @@ -1214,7 +1297,7 @@ class DataObject(abc.ABC): return {self.name: self.decoded} @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. Args: do : binary encoded bytes @@ -1227,7 +1310,7 @@ class DataObject(abc.ABC): 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 representation is _not_ returned, but just internalized in the object instance! Args: @@ -1236,7 +1319,8 @@ class DataObject(abc.ABC): bytes remaining at end of 'do' after parsing one TLV/DO. """ if do[0] != self.tag: - raise ValueError('%s: Can only decode tag 0x%02x' % (self, self.tag)) + raise ValueError('%s: Can only decode tag 0x%02x' % + (self, self.tag)) length = do[1] val = do[2:2+length] self.from_bytes(val) @@ -1252,7 +1336,7 @@ class DataObject(abc.ABC): return bytes(self._compute_tag()) + bytes(len(val)) + val # '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. Args: binary : binary bytes of encoded data @@ -1270,13 +1354,15 @@ class DataObject(abc.ABC): def encode(self) -> bytes: return self.to_tlv() + class TL0_DataObject(DataObject): """Data Object that has Tag, Len=0 and no Value part.""" - def __init__(self, name:str, desc:str, tag:int, val=None): + + def __init__(self, name: str, desc: str, tag: int, val=None): super().__init__(name, desc, tag) self.val = val - def from_bytes(self, binary:bytes): + def from_bytes(self, binary: bytes): if len(binary) != 0: raise ValueError self.decoded = self.val @@ -1289,14 +1375,15 @@ class DataObjectCollection: """A DataObjectCollection consits of multiple Data Objects identified by their tags. A given encoded DO may contain any of them in any order, and may contain multiple instances of each DO.""" - def __init__(self, name:str, desc:str = None, members=None): + + def __init__(self, name: str, desc: str = None, members=None): self.name = name self.desc = desc self.members = members or [] self.members_by_tag = {} self.members_by_name = {} - 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_tag = {m.tag: m for m in members} + self.members_by_name = {m.name: m for m in members} def __str__(self) -> str: member_strs = [str(x) for x in self.members] @@ -1319,7 +1406,7 @@ class DataObjectCollection: raise TypeError # '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, or uninitialized memory (0xFF) is found. Args: @@ -1332,7 +1419,7 @@ class DataObjectCollection: # iterate until no binary trailer is left while len(remainder): tag = remainder[0] - if tag == 0xff: # uninitialized memory at the end? + if tag == 0xff: # uninitialized memory at the end? return (res, remainder) if not tag in self.members_by_tag: raise ValueError('%s: Unknown Tag 0x%02x in %s; expected %s' % @@ -1352,10 +1439,12 @@ class DataObjectCollection: res.append(obj.to_tlv()) return res + class DataObjectChoice(DataObjectCollection): """One Data Object from within a choice, identified by its tag. This means that exactly one member of the choice must occur, and which one occurs depends on the tag.""" + def __add__(self, other): """We overload the add operator here to avoid inheriting it from DataObjecCollection.""" raise TypeError @@ -1373,7 +1462,7 @@ class DataObjectChoice(DataObjectCollection): raise TypeError # '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. Args: binary : binary bytes of encoded data @@ -1395,12 +1484,14 @@ class DataObjectChoice(DataObjectCollection): obj = self.members_by_name(decoded[0]) return obj.to_tlv() + class DataObjectSequence: """A sequence of DataObjects or DataObjectChoices. This allows us to express a certain ordered sequence of DOs or choices of DOs that have to appear as per the specification. By wrapping them into this formal DataObjectSequence, we can offer convenience methods for encoding or decoding an entire sequence.""" - def __init__(self, name:str, desc:str=None, sequence=None): + + def __init__(self, name: str, desc: str = None, sequence=None): self.sequence = sequence or [] self.name = name self.desc = desc @@ -1416,14 +1507,14 @@ class DataObjectSequence: def __add__(self, other) -> 'DataObjectSequence': """Add (append) a DataObject or DataObjectChoice to the sequence.""" if isinstance(other, 'DataObject'): - return DataObjectSequence(self.name, self.desc, self.sequence + [other]) + return DataObjectSequence(self.name, self.desc, self.sequence + [other]) elif isinstance(other, 'DataObjectChoice'): - return DataObjectSequence(self.name, self.desc, self.sequence + [other]) + return DataObjectSequence(self.name, self.desc, self.sequence + [other]) elif isinstance(other, 'DataObjectSequence'): - return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence) + return DataObjectSequence(self.name, self.desc, self.sequence + other.sequence) # '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. Args: binary : binary bytes of encoded data @@ -1439,7 +1530,7 @@ class DataObjectSequence: return (res, remainder) # '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. Args: do : binary input data to be decoded @@ -1469,8 +1560,10 @@ class DataObjectSequence: i += 1 return encoded + class CardCommand: """A single card command / instruction.""" + def __init__(self, name, ins, cla_list=None, desc=None): self.name = name self.ins = ins @@ -1503,9 +1596,10 @@ class CardCommand: class CardCommandSet: """A set of card instructions, typically specified within one spec.""" + def __init__(self, name, cmds=[]): self.name = name - self.cmds = { c.ins : c for c in cmds } + self.cmds = {c.ins: c for c in cmds} def __str__(self): return self.name @@ -1523,7 +1617,8 @@ class CardCommandSet: for c in other.cmds.keys(): self.cmds[c] = other.cmds[c] else: - raise ValueError('%s: Unsupported type to add operator: %s' % (self, other)) + raise ValueError( + '%s: Unsupported type to add operator: %s' % (self, other)) def lookup(self, ins, cla=None): """look-up the command within the CommandSet.""" @@ -1535,6 +1630,7 @@ class CardCommandSet: return None return cmd + def all_subclasses(cls) -> set: - """Recursively get all subclasses of a specified class""" - return set(cls.__subclasses__()).union([s for c in cls.__subclasses__() for s in all_subclasses(c)]) + """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)])