laforge has submitted this change. ( https://gerrit.osmocom.org/c/pysim/+/33691 )
Change subject: split pySim/legacy/{cards,utils} from pySim/{cards,utils} ......................................................................
split pySim/legacy/{cards,utils} from pySim/{cards,utils}
There are some functions / classes which are only needed by the legacy tools pySim-{read,prog}, bypassing our modern per-file transcoder classes. Let's move this code to the pySim/legacy sub-directory, rendering pySim.legacy.* module names.
The long-term goal is to get rid of those and have all code use the modern pySim/filesystem classes for reading/decoding/encoding/writing any kind of data on cards.
Change-Id: Ia8cf831929730c48f90679a83d69049475cc5077 --- M pySim-prog.py M pySim-read.py M pySim-shell.py M pySim-trace.py M pySim/cards.py A pySim/legacy/__init__.py A pySim/legacy/cards.py A pySim/legacy/utils.py M pySim/utils.py M setup.py M tests/test_utils.py 11 files changed, 1,942 insertions(+), 1,926 deletions(-)
Approvals: Jenkins Builder: Verified laforge: Looks good to me, approved
diff --git a/pySim-prog.py b/pySim-prog.py index 028db55..2e9d5d7 100755 --- a/pySim-prog.py +++ b/pySim-prog.py @@ -36,7 +36,7 @@
from pySim.commands import SimCardCommands from pySim.transport import init_reader -from pySim.cards import _cards_classes, card_detect +from pySim.legacy.cards import _cards_classes, card_detect from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn, dec_iccid from pySim.ts_51_011 import EF, EF_AD from pySim.card_handler import * diff --git a/pySim-read.py b/pySim-read.py index f80509c..52c60be 100755 --- a/pySim-read.py +++ b/pySim-read.py @@ -35,10 +35,9 @@ from pySim.commands import SimCardCommands from pySim.transport import init_reader, argparse_add_reader_args from pySim.exceptions import SwMatchError -from pySim.cards import card_detect, SimCard, UsimCard, IsimCard -from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_msisdn -from pySim.utils import format_xplmn_w_act, dec_st -from pySim.utils import h2s, format_ePDGSelection +from pySim.legacy.cards import card_detect, SimCard, UsimCard, IsimCard +from pySim.utils import h2b, h2s, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_msisdn +from pySim.legacy.utils import format_xplmn_w_act, dec_st
option_parser = argparse.ArgumentParser(description='Legacy tool for reading some parts of a SIM card', formatter_class=argparse.ArgumentDefaultsHelpFormatter) diff --git a/pySim-shell.py b/pySim-shell.py index a65e42a..4d4f2e2 100755 --- a/pySim-shell.py +++ b/pySim-shell.py @@ -49,7 +49,7 @@ from pySim.exceptions import * from pySim.commands import SimCardCommands from pySim.transport import init_reader, ApduTracer, argparse_add_reader_args, ProactiveHandler -from pySim.cards import card_detect, SimCard, UsimCard +from pySim.cards import card_detect, SimCardBase, UiccCardBase from pySim.utils import h2b, b2h, i2h, swap_nibbles, rpad, JsonEncoder, bertlv_parse_one, sw_match from pySim.utils import sanitize_pin_adm, tabulate_str_list, boxed_heading_str, Hexstr from pySim.card_handler import CardHandler, CardHandlerAuto @@ -95,10 +95,10 @@ return None, None
generic_card = False - card = card_detect("auto", scc) + card = card_detect(scc) if card is None: print("Warning: Could not detect card type - assuming a generic card type...") - card = SimCard(scc) + card = SimCardBase(scc) generic_card = True
profile = CardProfile.pick(scc) @@ -132,7 +132,7 @@ # IF we don't do this, we will have a SimCard but try USIM specific commands like # the update_ust method (see https://osmocom.org/issues/6055) if generic_card: - card = UsimCard(scc) + card = UiccCardBase(scc)
# Create runtime state with card profile rs = RuntimeState(card, profile) diff --git a/pySim-trace.py b/pySim-trace.py index bb1805c..4c8696d 100755 --- a/pySim-trace.py +++ b/pySim-trace.py @@ -8,7 +8,7 @@ from pySim.apdu import * from pySim.filesystem import RuntimeState
-from pySim.cards import UsimCard +from pySim.cards import UiccCardBase from pySim.commands import SimCardCommands from pySim.profile import CardProfile from pySim.ts_102_221 import CardProfileUICCSIM @@ -36,13 +36,13 @@
class DummySimLink(LinkBase): """A dummy implementation of the LinkBase abstract base class. Currently required - as the UsimCard doesn't work without SimCardCommands, which in turn require + as the UiccCardBase doesn't work without SimCardCommands, which in turn require a LinkBase implementation talking to a card.
In the tracer, we don't actually talk to any card, so we simply drop everything and claim it is successful.
- The UsimCard / SimCardCommands should be refactored to make this obsolete later.""" + The UiccCardBase / SimCardCommands should be refactored to make this obsolete later.""" def __init__(self, debug: bool = False, **kwargs): super().__init__(**kwargs) self._debug = debug @@ -75,7 +75,7 @@ profile.add_application(CardApplicationUSIM()) profile.add_application(CardApplicationISIM()) scc = SimCardCommands(transport=DummySimLink()) - card = UsimCard(scc) + card = UiccCardBase(scc) self.rs = RuntimeState(card, profile) # APDU Decoder self.ad = ApduDecoder(ApduCommands) diff --git a/pySim/cards.py b/pySim/cards.py index 4481117..f44c54f 100644 --- a/pySim/cards.py +++ b/pySim/cards.py @@ -23,6 +23,8 @@ #
from typing import Optional, Dict, Tuple +from pySim.ts_102_221 import EF_DIR, EF_ICCID +from pySim.ts_51_011 import DF_GSM import abc
from pySim.utils import * @@ -65,21 +67,23 @@ # callers having to do hasattr('read_aids') ahead of every call. return []
+ def read_iccid(self): + ef_iccid = EF_ICCID() + (res, sw) = self._scc.read_binary(ef_iccid.fid) + if sw == '9000': + return (dec_iccid(res), sw) + else: + return (None, sw) +
class SimCardBase(CardBase): - """Here we only add methods for commands specified in TS 51.011, without any higher-layer processing.""" - name = 'SIM'
- 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 probe(self): + df_gsm = DF_GSM() + return self.file_exists(df_gsm.fid)
class UiccCardBase(SimCardBase): @@ -90,244 +94,21 @@ # See also: ETSI TS 102 221, Table 9.3 self._adm_chv_num = 0xA0
-################################################################################ -# LEGACY -################################################################################ - -from smartcard.util import toBytes -from pytlv.TLV import * - -from pySim.ts_51_011 import EF, DF, EF_AD, EF_SPN -from pySim.ts_31_102 import EF_USIM_ADF_map -from pySim.ts_31_103 import EF_ISIM_ADF_map - -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(SimCardBase): - """Higher-layer class that is used *only* by legacy pySim-{prog,read}.""" - - def __init__(self, scc): - self._adm_chv_num = 4 - super().__init__(scc) - - 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 update_iccid(self, iccid): - data, sw = self._scc.update_binary(EF['ICCID'], enc_iccid(iccid)) - return 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_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 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 - - 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 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_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_ad(self, mnc=None, opmode=None, ofm=None, path=EF['AD']): - """ - 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. - - 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 - path (optional list with file path e.g. ['3f00', '7f20', '6fad']) - - Returns: - str: Return code of write operation - """ - - ad = EF_AD() - - # read from card - raw_hex_data, sw = self._scc.read_binary( - path, length=None, offset=0) - abstract_data = ad.decode_hex(raw_hex_data) - - # perform updates - if mnc and abstract_data['extensions']: - # Note: Since we derive the length of the MNC by the string length - # of the mnc parameter, the caller must ensure that mnc has the - # correct length and is padded with zeros (if necessary). - mnclen = len(str(mnc)) - if mnclen > 3 or mnclen < 2: - raise RuntimeError('invalid length of mnc "{}", expecting 2 or 3 digits'.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(path, 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 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_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) - - -class UsimCard(UiccCardBase, SimCard): - """Higher-layer class that is used *only* by legacy pySim-{prog,read}.""" + def probe(self): + # EF.DIR is a mandatory EF on all ICCIDs; however it *may* also exist on a TS 51.011 SIM + ef_dir = EF_DIR() + return self.file_exists(ef_dir.fid)
def read_aids(self): """Fetch all the AIDs present on UICC""" self._aids = [] try: + ef_dir = EF_DIR() # 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']) + rec_cnt = self._scc.record_count(ef_dir.fid) for i in range(0, rec_cnt): - rec = self._scc.read_record(EF['DIR'], i + 1) + rec = self._scc.read_record(ef_dir.fid, 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]) @@ -380,1431 +161,15 @@ return self._scc.select_adf(aid) return (None, None)
+def card_detect(scc): + # UICC always has higher preference, as a UICC might also contain a SIM application + uicc = UiccCardBase(scc) + if uicc.probe(): + return uicc
- 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) + # this is for detecting a real, classic TS 11.11 SIM card without any UICC support + sim = SimCardBase(scc) + if sim.probe(): + return sim
- 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_fplmn(self): - res, sw = self._scc.read_binary(EF_USIM_ADF_map['FPLMN']) - if sw == '9000': - return format_xplmn(res), sw - else: - return None, sw - - def update_fplmn(self, fplmn): - self._scc.select_file('3f00') - self.select_adf_by_aid('USIM') - size = self._scc.binary_size(EF_USIM_ADF_map['FPLMN']) - encoded = ''.join([enc_plmn(plmn[:3], plmn[3:]) for plmn in fplmn]) - encoded = rpad(encoded, size) - data, sw = self._scc.update_binary(EF_USIM_ADF_map['FPLMN'], encoded) - 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 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 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 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_est(self, service, bit=1): - (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['EST']) - if sw == '9000': - content = enc_st(res, service, bit) - (res, sw) = self._scc.update_binary( - EF_USIM_ADF_map['EST'], content) - return sw - - - -class IsimCard(UiccCardBase): - """Higher-layer class that is used *only* by legacy pySim-{prog,read}.""" - - name = 'ISIM' - - 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 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') - - # 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 - - 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}) - - 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 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}) - - 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 update_ist(self, service, bit=1): - (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IST']) - if sw == '9000': - content = enc_st(res, service, bit) - (res, sw) = self._scc.update_binary( - EF_ISIM_ADF_map['IST'], content) - return sw - - -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 - - * 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 - - * 3f00/7f4d/8f0d : Programmable Binary EFs - - * 3f00/7f4d/8f0e : Programmable Record EFs - - """ - - _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 - - return kls(scc) - - 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 - - if (rec_cnt < 1) or (rec_len != f[1]): - raise RuntimeError('Bad card type') - - return rec_cnt - - 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']) - - # 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 = '' - - # inline Ki - if self._ki_file is None: - v += p['ki'] - - # ICCID - v += '3f00' + '2fe2' + '0a' + enc_iccid(p['iccid']) - - # IMSI - v += '7f20' + '6f07' + '09' + enc_imsi(p['imsi']) - - # Ki - if self._ki_file: - v += self._ki_file + '10' + p['ki'] - - # 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) - - self._scc.update_record(self._files['b_ef'][0], 1, - rpad(v, self._files['b_ef'][1]*2) - ) - - # 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) - - 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) - - # 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' - - _files = { - 'name': ('8f0c', 18, True), - 'b_ef': ('8f0d', 74, True), - 'r_ef': ('8f0e', 50, True), - } - - _ki_file = None - - -class MagicSim(MagicSimBase): - - name = 'magicsim' - - _files = { - 'name': ('8f0c', 18, True), - 'b_ef': ('8f0d', 130, True), - 'r_ef': ('8f0e', 102, False), - } - - _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. - """ - - name = 'fakemagicsim' - - @classmethod - def autodetect(kls, scc): - try: - if scc.record_size(['3f00', '000c']) != 0x5a: - return None - except: - return None - - return kls(scc) - - 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 - - if (rec_cnt < 1) or (rec_len != 0x5a): - raise RuntimeError('Bad card type') - - 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) - - 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() - - # 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() - - # 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. - """ - - name = 'grcardsim' - - @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")) - - # 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.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.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) - - # 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 - - # 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' - - @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' - - @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") - - # 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 - """ - - 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 - - def program(self, p): - - # select MF - r = self._scc.select_path(['3f00']) - - # 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") - - pdu = 'A0D43A0508' + b2h(pin) - data, sw = self._scc._tp.send_apdu(pdu) - - # authenticate as ADM (enough to write file, and can set PINs) - - self._scc.verify_chv(0x05, pin) - - # write EF.ICCID - data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) - - # 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.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)) - - # 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) - - # 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)) - - -class SysmoUSIMSJS1(UsimCard): - """ - sysmocom 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 - - @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 program(self, p): - self.verify_adm(h2b(p['pin_adm'])) - - # select MF - r = self._scc.select_path(['3f00']) - - # write EF.ICCID - data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) - - # select DF_GSM - r = self._scc.select_path(['7f20']) - - # 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 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']) - - # 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.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.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.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 - - r = self._scc.select_path(['3f00', '7f10']) - data, sw = self._scc.update_record('6F40', 1, data, force_len=True) - - -class FairwavesSIM(UsimCard): - """ - 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 - """ - - 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 - - @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 read_ki(self): - """ - Read Ki in proprietary file. - - Requires ADM1 access level - """ - return self._scc.read_binary(self._EF['Ki']) - - def update_ki(self, ki): - """ - Set Ki in proprietary file. - - Requires ADM1 access level - """ - data, sw = self._scc.update_binary(self._EF['Ki'], ki) - return sw - - 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_op(self, op): - """ - Set OP in proprietary file. - - 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. - - 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 - - """ - - name = 'OpenCells-SIM' - - 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 - - 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']) - - # write EF.ICCID - data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) - - r = self._scc.select_path(['7ff0']) - - # set Ki in proprietary file - data, sw = self._scc.update_binary('FF02', p['ki']) - - # set OPC in proprietary file - data, sw = self._scc.update_binary('FF01', p['opc']) - - # 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 - - """ - - 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 - - @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'])) - - # 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 proprietary 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 proprietary 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.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.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.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) - - return None - - -class SysmoISIMSJA2(UsimCard, IsimCard): - """ - sysmocom 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 - - @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 #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 program(self, p): - self.verify_adm(h2b(p['pin_adm'])) - - # Populate AIDs - self.read_aids() - - # 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']) - - # 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'])) - - # 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.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.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.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) - - # EF.ACC - if p.get('acc'): - sw = self.update_acc(p['acc']) - if sw != '9000': - print("Programming ACC failed with code %s" % sw) - - # 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 - if self.adf_present("isim"): - self.select_adf_by_aid(adf="isim") - - 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.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) - - # 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) - - if self.adf_present("usim"): - self.select_adf_by_aid(adf="usim") - - # EF.AD in ADF.USIM - 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'), - path=EF_USIM_ADF_map['AD']) - if sw != '9000': - print("Programming AD failed with code %s" % sw) - - # 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.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) - - # 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 - -class SysmoISIMSJA5(SysmoISIMSJA2): - """ - sysmocom sysmoISIM-SJA5 - """ - - name = 'sysmoISIM-SJA5' - - @classmethod - def autodetect(kls, scc): - try: - # Try card model #1 (9FJ) - atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 51 CC" - if scc.get_atr() == toBytes(atr): - return kls(scc) - # Try card model #2 (SLM17) - atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 65 F8" - if scc.get_atr() == toBytes(atr): - return kls(scc) - # Try card model #3 (9FV) - atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 59 C4" - if scc.get_atr() == toBytes(atr): - return kls(scc) - except: - return None - return None - - -class GialerSim(UsimCard): - """ - Gialer sim cards (www.gialer.com). - """ - name = 'gialersim' - - def __init__(self, ssc): - super().__init__(ssc) - self._program_handlers = { - 'iccid': self.update_iccid, - 'imsi': self.update_imsi, - 'acc': self.update_acc, - 'smsp': self.update_smsp, - 'ki': self.update_ki, - 'opc': self.update_opc, - 'fplmn': self.update_fplmn, - } - - @classmethod - def autodetect(cls, scc): - try: - # Look for ATR - if scc.get_atr() == toBytes('3B 9F 95 80 1F C7 80 31 A0 73 B6 A1 00 67 CF 32 15 CA 9C D7 09 20'): - return cls(scc) - except: - return None - return None - - def program(self, p): - self.set_apdu_parameter('00', '0004') - # Authenticate - self._scc.verify_chv(0xc, h2b('3834373936313533')) - for handler in self._program_handlers: - if p.get(handler) is not None: - self._program_handlers[handler](p[handler]) - - mcc = p.get('mcc') - mnc = p.get('mnc') - has_plmn = mcc is not None and mnc is not None - # EF.HPLMN - if has_plmn: - self.update_hplmn_act(mcc, mnc) - - # EF.AD - if has_plmn or (p.get('opmode') is not None): - self.update_ad(mnc=mnc, opmode=p.get('opmode')) - - def update_smsp(self, smsp): - data, sw = self._scc.update_record(EF['SMSP'], 1, rpad(smsp, 80)) - return sw - - def update_ki(self, ki): - self._scc.select_path(['3f00', '0001']) - self._scc.update_binary('0001', ki) - - def update_opc(self, opc): - self._scc.select_path(['3f00', '6002']) - # No idea why the '01' is required - self._scc.update_binary('6002', '01' + opc) - - -# In order for autodetection ... -_cards_classes = [FakeMagicSim, SuperSim, MagicSim, GrcardSim, - SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1, - FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2, - SysmoISIMSJA5, GialerSim] - - -def card_detect(ctype, scc): - # 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 card is None: - print("Autodetection failed") - return None - - elif ctype in ctypes: - card = ctypes[ctype](scc) - - else: - raise ValueError("Unknown card type: %s" % ctype) - - return card + return None diff --git a/pySim/legacy/__init__.py b/pySim/legacy/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pySim/legacy/__init__.py diff --git a/pySim/legacy/cards.py b/pySim/legacy/cards.py new file mode 100644 index 0000000..e63b044 --- /dev/null +++ b/pySim/legacy/cards.py @@ -0,0 +1,1666 @@ +################################################################################ +# LEGACY +################################################################################ + +import abc +from smartcard.util import toBytes +from pytlv.TLV import * + +from pySim.cards import SimCardBase, UiccCardBase +from pySim.utils import dec_iccid, enc_iccid, dec_imsi, enc_imsi, dec_msisdn, enc_msisdn +from pySim.utils import dec_addr_tlv, enc_addr_tlv +from pySim.utils import enc_plmn, get_addr_type +from pySim.utils import is_hex, h2b, b2h, h2s, s2h, lpad, rpad +from pySim.legacy.utils import enc_ePDGSelection, format_xplmn_w_act, format_xplmn, dec_st, enc_st +from pySim.legacy.utils import format_ePDGSelection + +from pySim.ts_51_011 import EF, DF, EF_AD, EF_SPN +from pySim.ts_31_102 import EF_USIM_ADF_map +from pySim.ts_31_103 import EF_ISIM_ADF_map + +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(SimCardBase): + """Higher-layer class that is used *only* by legacy pySim-{prog,read}.""" + + def __init__(self, scc): + self._adm_chv_num = 4 + super().__init__(scc) + + 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 verify_adm(self, key): + """Authenticate with ADM key""" + (res, sw) = self._scc.verify_chv(self._adm_chv_num, key) + return sw + + def update_iccid(self, iccid): + data, sw = self._scc.update_binary(EF['ICCID'], enc_iccid(iccid)) + return 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_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 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 + + 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 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_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_ad(self, mnc=None, opmode=None, ofm=None, path=EF['AD']): + """ + 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. + + 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 + path (optional list with file path e.g. ['3f00', '7f20', '6fad']) + + Returns: + str: Return code of write operation + """ + + ad = EF_AD() + + # read from card + raw_hex_data, sw = self._scc.read_binary( + path, length=None, offset=0) + abstract_data = ad.decode_hex(raw_hex_data) + + # perform updates + if mnc and abstract_data['extensions']: + # Note: Since we derive the length of the MNC by the string length + # of the mnc parameter, the caller must ensure that mnc has the + # correct length and is padded with zeros (if necessary). + mnclen = len(str(mnc)) + if mnclen > 3 or mnclen < 2: + raise RuntimeError('invalid length of mnc "{}", expecting 2 or 3 digits'.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(path, 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 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_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) + + +class UsimCard(UiccCardBase, SimCard): + """Higher-layer class that is used *only* by legacy pySim-{prog,read}.""" + + 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 read_fplmn(self): + res, sw = self._scc.read_binary(EF_USIM_ADF_map['FPLMN']) + if sw == '9000': + return format_xplmn(res), sw + else: + return None, sw + + def update_fplmn(self, fplmn): + self._scc.select_file('3f00') + self.select_adf_by_aid('USIM') + size = self._scc.binary_size(EF_USIM_ADF_map['FPLMN']) + encoded = ''.join([enc_plmn(plmn[:3], plmn[3:]) for plmn in fplmn]) + encoded = rpad(encoded, size) + data, sw = self._scc.update_binary(EF_USIM_ADF_map['FPLMN'], encoded) + 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 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 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 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_est(self, service, bit=1): + (res, sw) = self._scc.read_binary(EF_USIM_ADF_map['EST']) + if sw == '9000': + content = enc_st(res, service, bit) + (res, sw) = self._scc.update_binary( + EF_USIM_ADF_map['EST'], content) + return sw + + + +class IsimCard(UiccCardBase): + """Higher-layer class that is used *only* by legacy pySim-{prog,read}.""" + + name = 'ISIM' + + 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 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') + + # 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 + + 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}) + + 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 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}) + + 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 update_ist(self, service, bit=1): + (res, sw) = self._scc.read_binary(EF_ISIM_ADF_map['IST']) + if sw == '9000': + content = enc_st(res, service, bit) + (res, sw) = self._scc.update_binary( + EF_ISIM_ADF_map['IST'], content) + return sw + + +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 + + * 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 + + * 3f00/7f4d/8f0d : Programmable Binary EFs + + * 3f00/7f4d/8f0e : Programmable Record EFs + + """ + + _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 + + return kls(scc) + + 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 + + if (rec_cnt < 1) or (rec_len != f[1]): + raise RuntimeError('Bad card type') + + return rec_cnt + + 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']) + + # 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 = '' + + # inline Ki + if self._ki_file is None: + v += p['ki'] + + # ICCID + v += '3f00' + '2fe2' + '0a' + enc_iccid(p['iccid']) + + # IMSI + v += '7f20' + '6f07' + '09' + enc_imsi(p['imsi']) + + # Ki + if self._ki_file: + v += self._ki_file + '10' + p['ki'] + + # 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) + + self._scc.update_record(self._files['b_ef'][0], 1, + rpad(v, self._files['b_ef'][1]*2) + ) + + # 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) + + 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) + + # 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' + + _files = { + 'name': ('8f0c', 18, True), + 'b_ef': ('8f0d', 74, True), + 'r_ef': ('8f0e', 50, True), + } + + _ki_file = None + + +class MagicSim(MagicSimBase): + + name = 'magicsim' + + _files = { + 'name': ('8f0c', 18, True), + 'b_ef': ('8f0d', 130, True), + 'r_ef': ('8f0e', 102, False), + } + + _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. + """ + + name = 'fakemagicsim' + + @classmethod + def autodetect(kls, scc): + try: + if scc.record_size(['3f00', '000c']) != 0x5a: + return None + except: + return None + + return kls(scc) + + 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 + + if (rec_cnt < 1) or (rec_len != 0x5a): + raise RuntimeError('Bad card type') + + 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) + + 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() + + # 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() + + # 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. + """ + + name = 'grcardsim' + + @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")) + + # 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.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.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) + + # 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 + + # 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' + + @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' + + @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") + + # 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 + """ + + 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 + + def program(self, p): + + # select MF + r = self._scc.select_path(['3f00']) + + # 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") + + pdu = 'A0D43A0508' + b2h(pin) + data, sw = self._scc._tp.send_apdu(pdu) + + # authenticate as ADM (enough to write file, and can set PINs) + + self._scc.verify_chv(0x05, pin) + + # write EF.ICCID + data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) + + # 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.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)) + + # 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) + + # 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)) + + +class SysmoUSIMSJS1(UsimCard): + """ + sysmocom 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 + + @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 program(self, p): + self.verify_adm(h2b(p['pin_adm'])) + + # select MF + r = self._scc.select_path(['3f00']) + + # write EF.ICCID + data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) + + # select DF_GSM + r = self._scc.select_path(['7f20']) + + # 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 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']) + + # 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.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.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.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 + + r = self._scc.select_path(['3f00', '7f10']) + data, sw = self._scc.update_record('6F40', 1, data, force_len=True) + + +class FairwavesSIM(UsimCard): + """ + 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 + """ + + 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 + + @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 read_ki(self): + """ + Read Ki in proprietary file. + + Requires ADM1 access level + """ + return self._scc.read_binary(self._EF['Ki']) + + def update_ki(self, ki): + """ + Set Ki in proprietary file. + + Requires ADM1 access level + """ + data, sw = self._scc.update_binary(self._EF['Ki'], ki) + return sw + + 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_op(self, op): + """ + Set OP in proprietary file. + + 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. + + 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 + + """ + + name = 'OpenCells-SIM' + + 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 + + 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']) + + # write EF.ICCID + data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid'])) + + r = self._scc.select_path(['7ff0']) + + # set Ki in proprietary file + data, sw = self._scc.update_binary('FF02', p['ki']) + + # set OPC in proprietary file + data, sw = self._scc.update_binary('FF01', p['opc']) + + # 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 + + """ + + 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 + + @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'])) + + # 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 proprietary 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 proprietary 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.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.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.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) + + return None + + +class SysmoISIMSJA2(UsimCard, IsimCard): + """ + sysmocom 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 + + @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 #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 program(self, p): + self.verify_adm(h2b(p['pin_adm'])) + + # Populate AIDs + self.read_aids() + + # 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']) + + # 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'])) + + # 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.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.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.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) + + # EF.ACC + if p.get('acc'): + sw = self.update_acc(p['acc']) + if sw != '9000': + print("Programming ACC failed with code %s" % sw) + + # 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 + if self.adf_present("isim"): + self.select_adf_by_aid(adf="isim") + + 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.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) + + # 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) + + if self.adf_present("usim"): + self.select_adf_by_aid(adf="usim") + + # EF.AD in ADF.USIM + 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'), + path=EF_USIM_ADF_map['AD']) + if sw != '9000': + print("Programming AD failed with code %s" % sw) + + # 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.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) + + # 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 + +class SysmoISIMSJA5(SysmoISIMSJA2): + """ + sysmocom sysmoISIM-SJA5 + """ + + name = 'sysmoISIM-SJA5' + + @classmethod + def autodetect(kls, scc): + try: + # Try card model #1 (9FJ) + atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 51 CC" + if scc.get_atr() == toBytes(atr): + return kls(scc) + # Try card model #2 (SLM17) + atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 65 F8" + if scc.get_atr() == toBytes(atr): + return kls(scc) + # Try card model #3 (9FV) + atr = "3B 9F 96 80 1F 87 80 31 E0 73 FE 21 1B 67 4A 35 75 30 35 02 59 C4" + if scc.get_atr() == toBytes(atr): + return kls(scc) + except: + return None + return None + + +class GialerSim(UsimCard): + """ + Gialer sim cards (www.gialer.com). + """ + name = 'gialersim' + + def __init__(self, ssc): + super().__init__(ssc) + self._program_handlers = { + 'iccid': self.update_iccid, + 'imsi': self.update_imsi, + 'acc': self.update_acc, + 'smsp': self.update_smsp, + 'ki': self.update_ki, + 'opc': self.update_opc, + 'fplmn': self.update_fplmn, + } + + @classmethod + def autodetect(cls, scc): + try: + # Look for ATR + if scc.get_atr() == toBytes('3B 9F 95 80 1F C7 80 31 A0 73 B6 A1 00 67 CF 32 15 CA 9C D7 09 20'): + return cls(scc) + except: + return None + return None + + def program(self, p): + self.set_apdu_parameter('00', '0004') + # Authenticate + self._scc.verify_chv(0xc, h2b('3834373936313533')) + for handler in self._program_handlers: + if p.get(handler) is not None: + self._program_handlers[handler](p[handler]) + + mcc = p.get('mcc') + mnc = p.get('mnc') + has_plmn = mcc is not None and mnc is not None + # EF.HPLMN + if has_plmn: + self.update_hplmn_act(mcc, mnc) + + # EF.AD + if has_plmn or (p.get('opmode') is not None): + self.update_ad(mnc=mnc, opmode=p.get('opmode')) + + def update_smsp(self, smsp): + data, sw = self._scc.update_record(EF['SMSP'], 1, rpad(smsp, 80)) + return sw + + def update_ki(self, ki): + self._scc.select_path(['3f00', '0001']) + self._scc.update_binary('0001', ki) + + def update_opc(self, opc): + self._scc.select_path(['3f00', '6002']) + # No idea why the '01' is required + self._scc.update_binary('6002', '01' + opc) + + +# In order for autodetection ... +_cards_classes = [FakeMagicSim, SuperSim, MagicSim, GrcardSim, + SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1, + FairwavesSIM, OpenCellsSim, WavemobileSim, SysmoISIMSJA2, + SysmoISIMSJA5, GialerSim] + + +def card_detect(ctype, scc): + # 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 card is None: + print("Autodetection failed") + return None + + elif ctype in ctypes: + card = ctypes[ctype](scc) + + else: + raise ValueError("Unknown card type: %s" % ctype) + + return card diff --git a/pySim/legacy/utils.py b/pySim/legacy/utils.py new file mode 100644 index 0000000..1b6b5d1 --- /dev/null +++ b/pySim/legacy/utils.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- + +""" pySim: various utilities only used by legacy tools (pySim-{prog,read}) +""" + +# Copyright (C) 2009-2010 Sylvain Munaut tnt@246tNt.com +# Copyright (C) 2021 Harald Welte laforge@osmocom.org +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# + +from pySim.utils import Hexstr, rpad, enc_plmn +from pySim.utils import dec_xplmn_w_act, dec_xplmn, dec_mcc_from_plmn, dec_mnc_from_plmn + +def hexstr_to_Nbytearr(s, nbytes): + return [s[i:i+(nbytes*2)] for i in range(0, len(s), (nbytes*2))] + +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 + + +def format_xplmn(hexstr: Hexstr) -> str: + s = "" + for rec_data in hexstr_to_Nbytearr(hexstr, 3): + rec_info = dec_xplmn(rec_data) + if not rec_info['mcc'] and not rec_info['mnc']: + rec_str = "unused" + else: + rec_str = "MCC: %s MNC: %s" % (rec_info['mcc'], rec_info['mnc']) + s += "\t%s # %s\n" % (rec_data, rec_str) + return s + + +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] + + # 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 + +def enc_st(st, service, state=1): + """ + 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 + + 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)] + + 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] + + return s + + +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 + """ + + 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)] + + 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 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. + + 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 + + +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. + """ + + 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 diff --git a/pySim/utils.py b/pySim/utils.py index 9defaf3..c25560c 100644 --- a/pySim/utils.py +++ b/pySim/utils.py @@ -435,30 +435,6 @@ 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) - - -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))] - # Accepts hex string representing three bytes
@@ -544,53 +520,6 @@ 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 - - -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 - - -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 - - -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 - - def dec_xplmn(threehexbytes: Hexstr) -> dict: res = {'mcc': 0, 'mnc': 0, 'act': []} plmn_chars = 6 @@ -601,18 +530,6 @@ 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 not rec_info['mcc'] and not rec_info['mnc']: - rec_str = "unused" - else: - rec_str = "MCC: %s MNC: %s" % (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 @@ -787,42 +704,6 @@ 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 - """ - - 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)] - - 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]) @@ -864,50 +745,6 @@ return ret
-def enc_st(st, service, state=1): - """ - 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 - - 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)] - - 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] - - return s - - def dec_addr_tlv(hexstr): """ Decode hex string to get EF.P-CSCF Address or EF.ePDGId or EF.ePDGIdEm. @@ -1038,88 +875,6 @@ 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. - - 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 - - -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. - """ - - 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] - - # 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 - - def get_addr_type(addr): """ Validates the given address and returns it's type (FQDN or IPv4 or IPv6) diff --git a/setup.py b/setup.py index 5678c55..f776443 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name='pySim', version='1.0', - packages=['pySim', 'pySim.transport', 'pySim.apdu', 'pySim.apdu_source'], + packages=['pySim', 'pySim.legacy', 'pySim.transport', 'pySim.apdu', 'pySim.apdu_source'], url='https://osmocom.org/projects/pysim/wiki', license='GPLv2', author_email='simtrace@lists.osmocom.org', diff --git a/tests/test_utils.py b/tests/test_utils.py index b7f790d..764cf71 100755 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -2,6 +2,7 @@
import unittest from pySim import utils +from pySim.legacy import utils as legacy_utils from pySim.ts_31_102 import EF_SUCI_Calc_Info
# we don't really want to thest TS 102 221, but the underlying DataObject codebase @@ -45,7 +46,7 @@ "ffffff0002", "ffffff0001", ] - self.assertEqual(utils.hexstr_to_Nbytearr(input_str, 5), expected) + self.assertEqual(legacy_utils.hexstr_to_Nbytearr(input_str, 5), expected)
def testDecMCCfromPLMN(self): self.assertEqual(utils.dec_mcc_from_plmn("92f501"), 295) @@ -130,7 +131,7 @@ expected += "\tffffff0000 # unused\n" expected += "\tffffff0000 # unused\n" expected += "\tffffff0000 # unused\n" - self.assertEqual(utils.format_xplmn_w_act(input_str), expected) + self.assertEqual(legacy_utils.format_xplmn_w_act(input_str), expected)
def testDecodeSuciCalcInfo(self):