├── .gitignore ├── .gitreview ├── COPYING ├── README.md ├── contrib ├── jenkins.sh ├── sim-rest-client.py ├── sim-rest-server.py └── sim-rest-server.service ├── csv-format ├── docs ├── Makefile ├── conf.py ├── index.rst ├── legacy.rst ├── library.rst ├── make.bat ├── shell.rst ├── suci-tutorial.rst └── trace.rst ├── pySim-prog.py ├── pySim-read.py ├── pySim-shell.py ├── pySim-trace.py ├── pySim ├── __init__.py ├── apdu │ ├── __init__.py │ ├── global_platform.py │ ├── ts_102_221.py │ └── ts_31_102.py ├── apdu_source │ ├── __init__.py │ ├── gsmtap.py │ ├── pyshark_gsmtap.py │ └── pyshark_rspro.py ├── app.py ├── ara_m.py ├── card_handler.py ├── card_key_provider.py ├── cards.py ├── cat.py ├── cdma_ruim.py ├── commands.py ├── construct.py ├── euicc.py ├── exceptions.py ├── filesystem.py ├── global_platform.py ├── gsm_r.py ├── gsmtap.py ├── iso7816_4.py ├── jsonpath.py ├── legacy │ ├── __init__.py │ ├── cards.py │ ├── ts_31_102.py │ ├── ts_31_103.py │ ├── ts_51_011.py │ └── utils.py ├── ota.py ├── profile.py ├── runtime.py ├── sms.py ├── sysmocom_sja2.py ├── tlv.py ├── transport │ ├── __init__.py │ ├── calypso.py │ ├── modem_atcmd.py │ ├── pcsc.py │ └── serial.py ├── ts_102_221.py ├── ts_102_222.py ├── ts_31_102.py ├── ts_31_102_telecom.py ├── ts_31_103.py ├── ts_31_104.py ├── ts_51_011.py └── utils.py ├── pyproject.toml ├── pysim-testdata ├── Fairwaves-SIM.data ├── Fairwaves-SIM.ok ├── Wavemobile-SIM.data ├── Wavemobile-SIM.ok ├── fakemagicsim.data ├── fakemagicsim.ok ├── pySim-trace_test_gsmtap.pcapng ├── pySim-trace_test_gsmtap.pcapng.ok ├── sysmoISIM-SJA2.data ├── sysmoISIM-SJA2.ok ├── sysmoUSIM-SJS1.data ├── sysmoUSIM-SJS1.ok ├── sysmosim-gr1.data └── sysmosim-gr1.ok ├── requirements.txt ├── scripts ├── deactivate-5g.script ├── deactivate-ims.script └── sysmoISIM-SJA2 │ └── dump-auth-cfg.pysim ├── setup.cfg ├── setup.py └── tests ├── pySim-prog_test.sh ├── pySim-trace_test.sh ├── test_apdu.py ├── test_construct.py ├── test_files.py ├── test_ota.py ├── test_sms.py ├── test_tlv.py └── test_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .*.swp 3 | 4 | /docs/_* 5 | /docs/generated 6 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=gerrit.osmocom.org 3 | project=pysim 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pySim - Read, Write and Browse Programmable SIM/USIM/ISIM/HPSIM Cards 2 | ===================================================================== 3 | 4 | This repository contains a number of Python programs that can be used 5 | to read, program (write) and browse all fields/parameters/files on 6 | SIM/USIM/ISIM/HPSIM cards used in 3GPP cellular networks from 2G to 5G. 7 | 8 | Note that the access control configuration of normal production cards 9 | issue by operators will restrict significantly which files a normal 10 | user can read, and particularly write to. 11 | 12 | The full functionality of pySim hence can only be used with on so-called 13 | programmable SIM/USIM/ISIM/HPSIM cards. 14 | 15 | Such SIM/USIM/ISIM/HPSIM cards are special cards, which - unlike those 16 | issued by regular commercial operators - come with the kind of keys that 17 | allow you to write the files/fields that normally only an operator can 18 | program. 19 | 20 | This is useful particularly if you are running your own cellular 21 | network, and want to configure your own SIM/USIM/ISIM/HPSIM cards for 22 | that network. 23 | 24 | 25 | Homepage 26 | -------- 27 | 28 | Please visit the [official homepage](https://osmocom.org/projects/pysim/wiki) 29 | for usage instructions, manual and examples. 30 | 31 | 32 | Documentation 33 | ------------- 34 | 35 | The pySim user manual can be built from this very source code by means 36 | of sphinx (with sphinxcontrib-napoleon and sphinx-argparse). See the 37 | Makefile in the 'docs' directory. 38 | 39 | A pre-rendered HTML user manual of the current pySim 'git master' is 40 | available from and 41 | a downloadable PDF version is published at 42 | . 43 | 44 | A slightly dated video presentation about pySim-shell can be found at 45 | . 46 | 47 | 48 | pySim-shell vs. legacy tools 49 | ---------------------------- 50 | 51 | While you will find a lot of online resources still describing the use of 52 | pySim-prog.py and pySim-read.py, those tools are considered legacy by 53 | now and have by far been superseded by the much more capable 54 | pySim-shell. We strongly encourage users to adopt pySim-shell, unless 55 | they have very specific requirements like batch programming of large 56 | quantities of cards, which is about the only remaining use case for the 57 | legacy tools. 58 | 59 | 60 | Git Repository 61 | -------------- 62 | 63 | You can clone from the official Osmocom git repository using 64 | ``` 65 | git clone https://gitea.osmocom.org/sim-card/pysim.git 66 | ``` 67 | 68 | There is a web interface at . 69 | 70 | 71 | Installation 72 | ------------ 73 | 74 | Please install the following dependencies: 75 | 76 | - bidict 77 | - cmd2 >= 1.5.0 78 | - colorlog 79 | - construct >= 2.9.51 80 | - gsm0338 81 | - jsonpath-ng 82 | - packaging 83 | - pycryptodomex 84 | - pyscard 85 | - pyserial 86 | - pytlv 87 | - pyyaml >= 5.1 88 | - smpp.pdu (from `github.com/hologram-io/smpp.pdu`) 89 | - termcolor 90 | 91 | Example for Debian: 92 | ```sh 93 | sudo apt-get install --no-install-recommends \ 94 | pcscd libpcsclite-dev \ 95 | python3 \ 96 | python3-setuptools \ 97 | python3-pycryptodome \ 98 | python3-pyscard \ 99 | python3-pip 100 | pip3 install --user -r requirements.txt 101 | ``` 102 | 103 | After installing all dependencies, the pySim applications ``pySim-read.py``, ``pySim-prog.py`` and ``pySim-shell.py`` may be started directly from the cloned repository. 104 | 105 | In addition to the dependencies above ``pySim-trace.py`` requires ``tshark`` and the python package ``pyshark`` to be installed. It is known that the ``tshark`` package 106 | in Debian versions before 11 may not work with pyshark. 107 | 108 | ### Archlinux Package 109 | 110 | Archlinux users may install the package ``python-pysim-git`` 111 | [![](https://img.shields.io/aur/version/python-pysim-git)](https://aur.archlinux.org/packages/python-pysim-git) 112 | from the [Arch User Repository (AUR)](https://aur.archlinux.org). 113 | The most convenient way is the use of an [AUR Helper](https://wiki.archlinux.org/index.php/AUR_helpers), 114 | e.g. [yay](https://aur.archlinux.org/packages/yay) or [pacaur](https://aur.archlinux.org/packages/pacaur). 115 | The following example shows the installation with ``yay``. 116 | 117 | ```sh 118 | # Install 119 | yay -Sy python-pysim-git 120 | 121 | # Uninstall 122 | sudo pacman -Rs python-pysim-git 123 | ``` 124 | 125 | 126 | Mailing List 127 | ------------ 128 | 129 | There is no separate mailing list for this project. However, 130 | discussions related to pysim-prog are happening on the 131 | mailing list, please see 132 | for subscription 133 | options and the list archive. 134 | 135 | Please observe the [Osmocom Mailing List 136 | Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules) 137 | when posting. 138 | 139 | 140 | Contributing 141 | ------------ 142 | 143 | Our coding standards are described at 144 | 145 | 146 | We are using a gerrit-based patch review process explained at 147 | 148 | -------------------------------------------------------------------------------- /contrib/jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -xe 2 | # jenkins build helper script for pysim. This is how we build on jenkins.osmocom.org 3 | # 4 | # environment variables: 5 | # * WITH_MANUALS: build manual PDFs if set to "1" 6 | # * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "1") 7 | # * JOB_TYPE: one of 'test', 'pylint', 'docs' 8 | # 9 | 10 | export PYTHONUNBUFFERED=1 11 | 12 | if [ ! -d "./pysim-testdata/" ] ; then 13 | echo "###############################################" 14 | echo "Please call from pySim-prog top directory" 15 | echo "###############################################" 16 | exit 1 17 | fi 18 | 19 | case "$JOB_TYPE" in 20 | "test") 21 | virtualenv -p python3 venv --system-site-packages 22 | . venv/bin/activate 23 | 24 | pip install -r requirements.txt 25 | pip install pyshark 26 | 27 | # Execute automatically discovered unit tests first 28 | python -m unittest discover -v -s tests/ 29 | 30 | # Run the test with physical cards 31 | cd pysim-testdata 32 | ../tests/pySim-prog_test.sh 33 | ../tests/pySim-trace_test.sh 34 | ;; 35 | "pylint") 36 | # Print pylint version 37 | pip3 freeze | grep pylint 38 | # Run pylint to find potential errors 39 | # Ignore E1102: not-callable 40 | # pySim/filesystem.py: E1102: method is not callable (not-callable) 41 | # Ignore E0401: import-error 42 | # pySim/utils.py:276: E0401: Unable to import 'Crypto.Cipher' (import-error) 43 | # pySim/utils.py:277: E0401: Unable to import 'Crypto.Util.strxor' (import-error) 44 | python3 -m pylint -j0 --errors-only \ 45 | --disable E1102 \ 46 | --disable E0401 \ 47 | --enable W0301 \ 48 | pySim *.py 49 | ;; 50 | "docs") 51 | rm -rf docs/_build 52 | make -C "docs" html latexpdf 53 | 54 | if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then 55 | make -C "docs" publish publish-html 56 | fi 57 | ;; 58 | *) 59 | set +x 60 | echo "ERROR: JOB_TYPE has unexpected value '$JOB_TYPE'." 61 | exit 1 62 | esac 63 | -------------------------------------------------------------------------------- /contrib/sim-rest-client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # sim-rest-client.py: client program to test the sim-rest-server.py 4 | # 5 | # this will generate authentication tuples just like a HLR / HSS 6 | # and will then send the related challenge to the REST interface 7 | # of sim-rest-server.py 8 | # 9 | # sim-rest-server.py will then contact the SIM card to perform the 10 | # authentication (just like a 3GPP RAN), and return the results via 11 | # the REST to sim-rest-client.py. 12 | # 13 | # (C) 2021 by Harald Welte 14 | # 15 | # This program is free software: you can redistribute it and/or modify 16 | # it under the terms of the GNU General Public License as published by 17 | # the Free Software Foundation, either version 2 of the License, or 18 | # (at your option) any later version. 19 | # 20 | # This program is distributed in the hope that it will be useful, 21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | # GNU General Public License for more details. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with this program. If not, see . 27 | 28 | from typing import Optional, Dict 29 | 30 | import sys 31 | import argparse 32 | import secrets 33 | import requests 34 | 35 | from CryptoMobile.Milenage import Milenage 36 | from CryptoMobile.utils import xor_buf 37 | 38 | def unpack48(x:bytes) -> int: 39 | """Decode a big-endian 48bit number from binary to integer.""" 40 | return int.from_bytes(x, byteorder='big') 41 | 42 | def pack48(x:int) -> bytes: 43 | """Encode a big-endian 48bit number from integer to binary.""" 44 | return x.to_bytes(48 // 8, byteorder='big') 45 | 46 | def milenage_generate(opc:bytes, amf:bytes, k:bytes, sqn:bytes, rand:bytes) -> Dict[str, bytes]: 47 | """Generate an MILENAGE Authentication Tuple.""" 48 | m = Milenage(None) 49 | m.set_opc(opc) 50 | mac_a = m.f1(k, rand, sqn, amf) 51 | res, ck, ik, ak = m.f2345(k, rand) 52 | 53 | # AUTN = (SQN ^ AK) || AMF || MAC 54 | sqn_ak = xor_buf(sqn, ak) 55 | autn = b''.join([sqn_ak, amf, mac_a]) 56 | 57 | return {'res': res, 'ck': ck, 'ik': ik, 'autn': autn} 58 | 59 | def milenage_auts(opc:bytes, k:bytes, rand:bytes, auts:bytes) -> Optional[bytes]: 60 | """Validate AUTS. If successful, returns SQN_MS""" 61 | amf = b'\x00\x00' # TS 33.102 Section 6.3.3 62 | m = Milenage(None) 63 | m.set_opc(opc) 64 | ak = m.f5star(k, rand) 65 | 66 | sqn_ak = auts[:6] 67 | sqn = xor_buf(sqn_ak, ak[:6]) 68 | 69 | mac_s = m.f1star(k, rand, sqn, amf) 70 | if mac_s == auts[6:14]: 71 | return sqn 72 | else: 73 | return False 74 | 75 | 76 | def build_url(suffix:str, base_path="/sim-auth-api/v1") -> str: 77 | """Build an URL from global server_host, server_port, BASE_PATH and suffix.""" 78 | return "http://%s:%u%s%s" % (server_host, server_port, base_path, suffix) 79 | 80 | 81 | def rest_post(suffix:str, js:Optional[dict] = None): 82 | """Perform a RESTful POST.""" 83 | url = build_url(suffix) 84 | if verbose: 85 | print("POST %s (%s)" % (url, str(js))) 86 | resp = requests.post(url, json=js) 87 | if verbose: 88 | print("-> %s" % (resp)) 89 | if not resp.ok: 90 | print("POST failed") 91 | return resp 92 | 93 | def rest_get(suffix:str, base_path=None): 94 | """Perform a RESTful GET.""" 95 | url = build_url(suffix, base_path) 96 | if verbose: 97 | print("GET %s" % url) 98 | resp = requests.get(url) 99 | if verbose: 100 | print("-> %s" % (resp)) 101 | if not resp.ok: 102 | print("GET failed") 103 | return resp 104 | 105 | 106 | def main_info(args): 107 | resp = rest_get('/slot/%u' % args.slot_nr, base_path="/sim-info-api/v1") 108 | if not resp.ok: 109 | print("<- ERROR %u: %s" % (resp.status_code, resp.text)) 110 | sys.exit(1) 111 | resp_json = resp.json() 112 | print("<- %s" % resp_json) 113 | 114 | 115 | def main_auth(args): 116 | #opc = bytes.fromhex('767A662ACF4587EB0C450C6A95540A04') 117 | #k = bytes.fromhex('876B2D8D403EE96755BEF3E0A1857EBE') 118 | opc = bytes.fromhex(args.opc) 119 | k = bytes.fromhex(args.key) 120 | amf = bytes.fromhex(args.amf) 121 | sqn = bytes.fromhex(args.sqn) 122 | 123 | for i in range(args.count): 124 | rand = secrets.token_bytes(16) 125 | t = milenage_generate(opc=opc, amf=amf, k=k, sqn=sqn, rand=rand) 126 | 127 | req_json = {'rand': rand.hex(), 'autn': t['autn'].hex()} 128 | print("-> %s" % req_json) 129 | resp = rest_post('/slot/%u' % args.slot_nr, req_json) 130 | if not resp.ok: 131 | print("<- ERROR %u: %s" % (resp.status_code, resp.text)) 132 | break 133 | resp_json = resp.json() 134 | print("<- %s" % resp_json) 135 | if 'synchronisation_failure' in resp_json: 136 | auts = bytes.fromhex(resp_json['synchronisation_failure']['auts']) 137 | sqn_ms = milenage_auts(opc, k, rand, auts) 138 | if sqn_ms is not False: 139 | print("SQN_MS = %s" % sqn_ms.hex()) 140 | sqn_ms_int = unpack48(sqn_ms) 141 | # we assume an IND bit-length of 5 here 142 | sqn = pack48(sqn_ms_int + (1 << 5)) 143 | else: 144 | raise RuntimeError("AUTS auth failure during re-sync?!?") 145 | elif 'successful_3g_authentication' in resp_json: 146 | auth_res = resp_json['successful_3g_authentication'] 147 | assert bytes.fromhex(auth_res['res']) == t['res'] 148 | assert bytes.fromhex(auth_res['ck']) == t['ck'] 149 | assert bytes.fromhex(auth_res['ik']) == t['ik'] 150 | # we assume an IND bit-length of 5 here 151 | sqn = pack48(unpack48(sqn) + (1 << 5)) 152 | else: 153 | raise RuntimeError("Auth failure") 154 | 155 | 156 | def main(argv): 157 | global server_port, server_host, verbose 158 | 159 | parser = argparse.ArgumentParser() 160 | parser.add_argument("-H", "--host", help="Host to connect to", default="localhost") 161 | parser.add_argument("-p", "--port", help="TCP port to connect to", default=8000) 162 | parser.add_argument("-v", "--verbose", help="increase output verbosity", action='count', default=0) 163 | parser.add_argument("-n", "--slot-nr", help="SIM slot number", type=int, default=0) 164 | subp = parser.add_subparsers() 165 | 166 | auth_p = subp.add_parser('auth', help='UMTS AKA Authentication') 167 | auth_p.add_argument("-c", "--count", help="Auth count", type=int, default=10) 168 | auth_p.add_argument("-k", "--key", help="Secret key K (hex)", type=str, required=True) 169 | auth_p.add_argument("-o", "--opc", help="Secret OPc (hex)", type=str, required=True) 170 | auth_p.add_argument("-a", "--amf", help="AMF Field (hex)", type=str, default="0000") 171 | auth_p.add_argument("-s", "--sqn", help="SQN Field (hex)", type=str, default="000000000000") 172 | auth_p.set_defaults(func=main_auth) 173 | 174 | info_p = subp.add_parser('info', help='Information about the Card') 175 | info_p.set_defaults(func=main_info) 176 | 177 | args = parser.parse_args() 178 | server_host = args.host 179 | server_port = args.port 180 | verbose = args.verbose 181 | args.func(args) 182 | 183 | 184 | if __name__ == "__main__": 185 | main(sys.argv) 186 | -------------------------------------------------------------------------------- /contrib/sim-rest-server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # RESTful HTTP service for performing authentication against USIM cards 4 | # 5 | # (C) 2021-2022 by Harald Welte 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 2 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU General Public License 18 | # along with this program. If not, see . 19 | 20 | import json 21 | import sys 22 | import argparse 23 | 24 | from klein import Klein 25 | 26 | from pySim.transport import ApduTracer 27 | from pySim.transport.pcsc import PcscSimLink 28 | from pySim.commands import SimCardCommands 29 | from pySim.cards import UiccCardBase 30 | from pySim.utils import dec_iccid, dec_imsi 31 | from pySim.ts_51_011 import EF_IMSI 32 | from pySim.ts_102_221 import EF_ICCID 33 | from pySim.exceptions import * 34 | 35 | class ApduPrintTracer(ApduTracer): 36 | def trace_response(self, cmd, sw, resp): 37 | #print("CMD: %s -> RSP: %s %s" % (cmd, sw, resp)) 38 | pass 39 | 40 | def connect_to_card(slot_nr:int): 41 | tp = PcscSimLink(slot_nr, apdu_tracer=ApduPrintTracer()) 42 | tp.connect() 43 | 44 | scc = SimCardCommands(tp) 45 | card = UiccCardBase(scc) 46 | 47 | # this should be part of UsimCard, but FairewavesSIM breaks with that :/ 48 | scc.cla_byte = "00" 49 | scc.sel_ctrl = "0004" 50 | 51 | card.read_aids() 52 | 53 | # ensure that MF is selected when we are done. 54 | card._scc.select_file('3f00') 55 | 56 | return tp, scc, card 57 | 58 | class ApiError: 59 | def __init__(self, msg:str, sw=None): 60 | self.msg = msg 61 | self.sw = sw 62 | 63 | def __str__(self): 64 | d = {'error': {'message':self.msg}} 65 | if self.sw: 66 | d['error']['status_word'] = self.sw 67 | return json.dumps(d) 68 | 69 | 70 | def set_headers(request): 71 | request.setHeader('Content-Type', 'application/json') 72 | 73 | class SimRestServer: 74 | app = Klein() 75 | 76 | @app.handle_errors(NoCardError) 77 | def no_card_error(self, request, failure): 78 | set_headers(request) 79 | request.setResponseCode(410) 80 | return str(ApiError("No SIM card inserted in slot")) 81 | 82 | @app.handle_errors(ReaderError) 83 | def reader_error(self, request, failure): 84 | set_headers(request) 85 | request.setResponseCode(404) 86 | return str(ApiError("Reader Error: Specified SIM Slot doesn't exist")) 87 | 88 | @app.handle_errors(ProtocolError) 89 | def protocol_error(self, request, failure): 90 | set_headers(request) 91 | request.setResponseCode(500) 92 | return str(ApiError("Protocol Error: %s" % failure.value)) 93 | 94 | @app.handle_errors(SwMatchError) 95 | def sw_match_error(self, request, failure): 96 | set_headers(request) 97 | request.setResponseCode(500) 98 | sw = failure.value.sw_actual 99 | if sw == '9862': 100 | return str(ApiError("Card Authentication Error - Incorrect MAC", sw)) 101 | elif sw == '6982': 102 | return str(ApiError("Security Status not satisfied - Card PIN enabled?", sw)) 103 | else: 104 | return str(ApiError("Card Communication Error %s" % failure.value, sw)) 105 | 106 | 107 | @app.route('/sim-auth-api/v1/slot/') 108 | def auth(self, request, slot): 109 | """REST API endpoint for performing authentication against a USIM. 110 | Expects a JSON body containing RAND and AUTN. 111 | Returns a JSON body containing RES, CK, IK and Kc.""" 112 | try: 113 | # there are two hex-string JSON parameters in the body: rand and autn 114 | content = json.loads(request.content.read()) 115 | rand = content['rand'] 116 | autn = content['autn'] 117 | except: 118 | set_headers(request) 119 | request.setResponseCode(400) 120 | return str(ApiError("Malformed Request")) 121 | 122 | tp, scc, card = connect_to_card(slot) 123 | 124 | card.select_adf_by_aid(adf='usim') 125 | res, sw = scc.authenticate(rand, autn) 126 | 127 | tp.disconnect() 128 | 129 | set_headers(request) 130 | return json.dumps(res, indent=4) 131 | 132 | @app.route('/sim-info-api/v1/slot/') 133 | def info(self, request, slot): 134 | """REST API endpoint for obtaining information about an USIM. 135 | Expects empty body in request. 136 | Returns a JSON body containing ICCID, IMSI.""" 137 | 138 | tp, scc, card = connect_to_card(slot) 139 | 140 | ef_iccid = EF_ICCID() 141 | (iccid, sw) = card._scc.read_binary(ef_iccid.fid) 142 | 143 | card.select_adf_by_aid(adf='usim') 144 | ef_imsi = EF_IMSI() 145 | (imsi, sw) = card._scc.read_binary(ef_imsi.fid) 146 | 147 | res = {"imsi": dec_imsi(imsi), "iccid": dec_iccid(iccid) } 148 | 149 | tp.disconnect() 150 | 151 | set_headers(request) 152 | return json.dumps(res, indent=4) 153 | 154 | 155 | def main(argv): 156 | parser = argparse.ArgumentParser() 157 | parser.add_argument("-H", "--host", help="Host/IP to bind HTTP to", default="localhost") 158 | parser.add_argument("-p", "--port", help="TCP port to bind HTTP to", default=8000) 159 | #parser.add_argument("-v", "--verbose", help="increase output verbosity", action='count', default=0) 160 | 161 | args = parser.parse_args() 162 | 163 | srr = SimRestServer() 164 | srr.app.run(args.host, args.port) 165 | 166 | if __name__ == "__main__": 167 | main(sys.argv) 168 | -------------------------------------------------------------------------------- /contrib/sim-rest-server.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Osmocom SIM REST server 3 | 4 | [Service] 5 | Type=simple 6 | # we listen to 0.0.0.0, allowing remote, unauthenticated clients to connect from everywhere! 7 | ExecStart=/usr/local/src/pysim/contrib/sim-rest-server.py -H 0.0.0.0 8 | Restart=always 9 | RestartSec=2 10 | # this user must be created beforehand; it must have PC/SC access 11 | User=rest 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /csv-format: -------------------------------------------------------------------------------- 1 | This file aims to describe the format of the CSV file pySim uses. 2 | 3 | The first line contains the fieldnames which will be used by pySim. This 4 | avoids having a specific order. 5 | 6 | The field names are the following: 7 | 8 | iccid: ICCID of the card. Used to identify the cards (with --read-iccid) 9 | imsi: IMSI of the card 10 | mcc: Mobile Country Code (optional) 11 | mnc: Mobile Network Code (optional) 12 | smsp: MSISDN of the SMSC (optional) 13 | ki: Ki 14 | opc: OPc 15 | acc: Access class of the SIM (optional) 16 | pin_adm: Admin PIN of the SIM. Needed to reprogram various files 17 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # for osmo-gsm-manuals 12 | OSMO_GSM_MANUALS_DIR ?= $(shell pkg-config osmo-gsm-manuals --variable=osmogsmmanualsdir 2>/dev/null) 13 | OSMO_REPOSITORY = "pysim" 14 | UPLOAD_FILES = $(BUILDDIR)/latex/osmopysim-usermanual.pdf 15 | CLEAN_FILES = $(UPLOAD_FILES) 16 | 17 | # Copy variables from Makefile.common.inc that are used in publish-html, 18 | # as Makefile.common.inc must be included after publish-html 19 | PUBLISH_REF ?= master 20 | PUBLISH_TEMPDIR = _publish_tmpdir 21 | SSH_COMMAND = ssh -o 'UserKnownHostsFile=$(OSMO_GSM_MANUALS_DIR)/build/known_hosts' -p 48 22 | 23 | # Put it first so that "make" without argument is like "make help". 24 | .PHONY: help 25 | help: 26 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 27 | 28 | $(BUILDDIR)/latex/pysim.pdf: latexpdf 29 | @/bin/true 30 | 31 | publish-html: html 32 | rm -rf "$(PUBLISH_TEMPDIR)" 33 | mkdir -p "$(PUBLISH_TEMPDIR)/pysim/$(PUBLISH_REF)" 34 | cp -r "$(BUILDDIR)"/html "$(PUBLISH_TEMPDIR)/pysim/$(PUBLISH_REF)" 35 | cd "$(PUBLISH_TEMPDIR)" && \ 36 | rsync \ 37 | -avzR \ 38 | -e "$(SSH_COMMAND)" \ 39 | "pysim" \ 40 | docs@ftp.osmocom.org:web-files/ 41 | rm -rf "$(PUBLISH_TEMPDIR)" 42 | 43 | # put this before the catch-all below 44 | include $(OSMO_GSM_MANUALS_DIR)/build/Makefile.common.inc 45 | 46 | 47 | # Catch-all target: route all unknown targets to Sphinx using the new 48 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 49 | %: 50 | @if [ "$@" != "shrink" ]; then \ 51 | $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O); \ 52 | fi 53 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('..')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'osmopysim-usermanual' 21 | copyright = '2009-2023 by Sylvain Munaut, Harald Welte, Philipp Maier, Supreeth Herle, Merlin Chlosta' 22 | author = 'Sylvain Munaut, Harald Welte, Philipp Maier, Supreeth Herle, Merlin Chlosta' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = [ 31 | "sphinx.ext.autodoc", 32 | "sphinxarg.ext", 33 | "sphinx.ext.autosectionlabel", 34 | "sphinx.ext.napoleon" 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = 'alabaster' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | 58 | autoclass_content = 'both' 59 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pysim documentation master file 2 | 3 | Welcome to Osmocom pySim 4 | ======================== 5 | 6 | Introduction 7 | ------------ 8 | 9 | pySim is a python implementation of various software that helps you with 10 | managing subscriber identity cards for cellular networks, so-called SIM 11 | cards. 12 | 13 | Many Osmocom (Open Source Mobile Communications) projects relate to operating 14 | private / custom cellular networks, and provisioning SIM cards for said networks 15 | is in many cases a requirement to operate such networks. 16 | 17 | To make use of most of pySim's features, you will need a `programmable` SIM card, 18 | i.e. a card where you are the owner/operator and have sufficient credentials (such 19 | as the `ADM PIN`) in order to write to many if not most of the files on the card. 20 | 21 | Such cards are, for example, available from sysmocom, a major contributor to pySim. 22 | See https://www.sysmocom.de/products/lab/sysmousim/ for more details. 23 | 24 | pySim supports classic GSM SIM cards as well as ETSI UICC with 3GPP USIM and ISIM 25 | applications. It is easily extensible, so support for additional files, card 26 | applications, etc. can be added easily by any python developer. We do encourage you 27 | to submit your contributions to help this collaborative development project. 28 | 29 | pySim consists of several parts: 30 | 31 | * a python :ref:`library` containing plenty of objects and methods that can be used for 32 | writing custom programs interfacing with SIM cards. 33 | * the [new] :ref:`interactive pySim-shell command line program` 34 | * the [new] :ref:`pySim-trace APDU trace decoder` 35 | * the [legacy] :ref:`pySim-prog and pySim-read tools` 36 | 37 | .. toctree:: 38 | :maxdepth: 3 39 | :caption: Contents: 40 | 41 | shell 42 | trace 43 | legacy 44 | library 45 | 46 | 47 | Indices and tables 48 | ================== 49 | 50 | * :ref:`genindex` 51 | * :ref:`modindex` 52 | * :ref:`search` 53 | -------------------------------------------------------------------------------- /docs/legacy.rst: -------------------------------------------------------------------------------- 1 | Legacy tools 2 | ============ 3 | 4 | *legacy tools* are the classic ``pySim-prog`` and ``pySim-read`` programs that 5 | existed long before ``pySim-shell``. 6 | 7 | These days, you should primarily use ``pySim-shell`` instead of these 8 | legacy tools. 9 | 10 | pySim-prog 11 | ---------- 12 | 13 | ``pySim-prog`` was the first part of the pySim software suite. It started as 14 | a tool to write ICCID, IMSI, MSISDN and Ki to very simplistic SIM cards, and 15 | was later extended to a variety of other cards. As the number of features supported 16 | became no longer bearable to express with command-line arguments, `pySim-shell` was 17 | created. 18 | 19 | Basic use cases can still use `pySim-prog`. 20 | 21 | Program customizable SIMs 22 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 23 | Two modes are possible: 24 | 25 | - one where you specify every parameter manually : 26 | 27 | ``./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -i -s `` 28 | 29 | 30 | - one where they are generated from some minimal set : 31 | 32 | ``./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -z -j `` 33 | 34 | With and , the soft will generate 35 | 'predictable' IMSI and ICCID, so make sure you choose them so as not to 36 | conflict with anyone. (for eg. your name as and 37 | 0 1 2 ... for ). 38 | 39 | You also need to enter some parameters to select the device : 40 | -t TYPE : type of card (supersim, magicsim, fakemagicsim or try 'auto') 41 | -d DEV : Serial port device (default /dev/ttyUSB0) 42 | -b BAUD : Baudrate (default 9600) 43 | 44 | 45 | pySim-read 46 | ---------- 47 | 48 | ``pySim-read`` allows you to read some data from a SIM card. It will only some files 49 | of the card, and will only read files accessible to a normal user (without any special authentication) 50 | 51 | These days, you should use the ``export`` command of ``pySim-shell`` 52 | instead. It performs a much more comprehensive export of all of the 53 | [standard] files that can be found on the card. To get a human-readable 54 | decode instead of the raw hex export, you can use ``export --json``. 55 | 56 | Specifically, pySim-read will dump the following: 57 | 58 | * MF 59 | 60 | * EF.ICCID 61 | 62 | * DF.GSM 63 | 64 | * EF,IMSI 65 | * EF.GID1 66 | * EF.GID2 67 | * EF.SMSP 68 | * EF.SPN 69 | * EF.PLMNsel 70 | * EF.PLMNwAcT 71 | * EF.OPLMNwAcT 72 | * EF.HPLMNAcT 73 | * EF.ACC 74 | * EF.MSISDN 75 | * EF.AD 76 | * EF.SST 77 | 78 | * ADF.USIM 79 | 80 | * EF.EHPLMN 81 | * EF.UST 82 | * EF.ePDGId 83 | * EF.ePDGSelection 84 | 85 | * ADF.ISIM 86 | 87 | * EF.PCSCF 88 | * EF.DOMAIN 89 | * EF.IMPI 90 | * EF.IMPU 91 | * EF.UICCIARI 92 | * EF.IST 93 | 94 | 95 | pySim-read usage 96 | ~~~~~~~~~~~~~~~~ 97 | 98 | .. argparse:: 99 | :module: pySim-read 100 | :func: option_parser 101 | :prog: pySim-read.py 102 | -------------------------------------------------------------------------------- /docs/library.rst: -------------------------------------------------------------------------------- 1 | pySim library 2 | ============= 3 | 4 | pySim filesystem abstraction 5 | ---------------------------- 6 | 7 | .. automodule:: pySim.filesystem 8 | :members: 9 | 10 | pySim commands abstraction 11 | -------------------------- 12 | 13 | .. automodule:: pySim.commands 14 | :members: 15 | 16 | pySim Transport 17 | --------------- 18 | 19 | The pySim.transport classes implement specific ways how to 20 | communicate with a SIM card. A "transport" provides ways 21 | to transceive APDUs with the card. 22 | 23 | The most commonly used transport uses the PC/SC interface to 24 | utilize a variety of smart card interfaces ("readers"). 25 | 26 | Transport base class 27 | ~~~~~~~~~~~~~~~~~~~~ 28 | 29 | .. automodule:: pySim.transport 30 | :members: 31 | 32 | 33 | calypso / OsmocomBB transport 34 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 35 | 36 | This allows the use of the SIM slot of an OsmocomBB compatible phone with the TI Calypso chipset, 37 | using the L1CTL interface to talk to the layer1.bin firmware on the phone. 38 | 39 | .. automodule:: pySim.transport.calypso 40 | :members: 41 | 42 | 43 | AT-command Modem transport 44 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 45 | 46 | This transport uses AT commands of a cellular modem in order to get access to the SIM card inserted 47 | in such a modem. 48 | 49 | .. automodule:: pySim.transport.modem_atcmd 50 | :members: 51 | 52 | 53 | PC/SC transport 54 | ~~~~~~~~~~~~~~~ 55 | 56 | PC/SC is the standard API for accessing smart card interfaces 57 | on all major operating systems, including the MS Windows Family, 58 | OS X as well as Linux / Unix OSs. 59 | 60 | .. automodule:: pySim.transport.pcsc 61 | :members: 62 | 63 | 64 | Serial/UART transport 65 | ~~~~~~~~~~~~~~~~~~~~~ 66 | 67 | This transport implements interfacing smart cards via 68 | very simplistic UART readers. These readers basically 69 | wire together the Rx+Tx pins of a RS232 UART, provide 70 | a fixed crystal oscillator for clock, and operate the UART 71 | at 9600 bps. These readers are sometimes called `Phoenix`. 72 | 73 | .. automodule:: pySim.transport.serial 74 | :members: 75 | 76 | 77 | pySim construct utilities 78 | ------------------------- 79 | 80 | .. automodule:: pySim.construct 81 | :members: 82 | 83 | pySim TLV utilities 84 | ------------------- 85 | 86 | .. automodule:: pySim.tlv 87 | :members: 88 | 89 | pySim utility functions 90 | ----------------------- 91 | 92 | .. automodule:: pySim.utils 93 | :members: 94 | 95 | pySim exceptions 96 | ---------------- 97 | 98 | .. automodule:: pySim.exceptions 99 | :members: 100 | 101 | pySim card_handler 102 | ------------------ 103 | 104 | .. automodule:: pySim.card_handler 105 | :members: 106 | 107 | pySim card_key_provider 108 | ----------------------- 109 | 110 | .. automodule:: pySim.card_key_provider 111 | :members: 112 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/suci-tutorial.rst: -------------------------------------------------------------------------------- 1 | 2 | Guide: Enabling 5G SUCI 3 | ======================== 4 | 5 | SUPI/SUCI Concealment is a feature of 5G-Standalone (SA) to encrypt the 6 | IMSI/SUPI with a network operator public key. 3GPP Specifies two different 7 | variants for this: 8 | 9 | * SUCI calculation *in the UE*, using data from the SIM 10 | * SUCI calculation *on the card itself* 11 | 12 | pySIM supports writing the 5G-specific files for *SUCI calculation in the UE* on USIM cards, assuming that 13 | your cards contain the required files, and you have the privileges/credentials to write to them. This is 14 | the case using sysmocom sysmoISIM-SJA2 cards (or successor products). 15 | 16 | In short, you can enable SUCI with these steps: 17 | 18 | * activate USIM **Service 124** 19 | * make sure USIM **Service 125** is disabled 20 | * store the public keys in **SUCI_Calc_Info** 21 | * set the **Routing Indicator** (required) 22 | 23 | If you want to disable the feature, you can just disable USIM Service 124 (and 125). 24 | 25 | Technical References 26 | ~~~~~~~~~~~~~~~~~~~~ 27 | 28 | This guide covers the basic workflow of provisioning SIM cards with the 5G SUCI feature. For detailed information on the SUCI feature and file contents, the following documents are helpful: 29 | 30 | * USIM files and structure: `TS 31.102 `__ 31 | * USIM tests (incl. file content examples) `TS 31.121 `__ 32 | 33 | For specific information on sysmocom SIM cards, refer to Section 9.1 of the `sysmoUSIM User 34 | Manual `__. 35 | 36 | -------------- 37 | 38 | Admin PIN 39 | --------- 40 | 41 | The usual way to authenticate yourself to the card as the cellular 42 | operator is to validate the so-called ADM1 (admin) PIN. This may differ 43 | from card model/vendor to card model/vendor. 44 | 45 | Start pySIM-shell and enter the admin PIN for your card. If you bought 46 | the SIM card from your network operator and don’t have the admin PIN, 47 | you cannot change SIM contents! 48 | 49 | Launch pySIM: 50 | 51 | :: 52 | 53 | $ ./pySim-shell.py -p 0 54 | 55 | Using PC/SC reader interface 56 | Autodetected card type: sysmoISIM-SJA2 57 | Welcome to pySim-shell! 58 | pySIM-shell (00:MF)> 59 | 60 | Enter the ADM PIN: 61 | 62 | :: 63 | 64 | pySIM-shell (00:MF)> verify_adm XXXXXXXX 65 | 66 | Otherwise, write commands will fail with ``SW Mismatch: Expected 9000 and got 6982.`` 67 | 68 | Key Provisioning 69 | ---------------- 70 | 71 | :: 72 | 73 | pySIM-shell (00:MF)> select MF 74 | pySIM-shell (00:MF)> select ADF.USIM 75 | pySIM-shell (00:MF/ADF.USIM)> select DF.5GS 76 | pySIM-shell (00:MF/ADF.USIM/DF.5GS)> select EF.SUCI_Calc_Info 77 | 78 | By default, the file is present but empty: 79 | 80 | :: 81 | 82 | pySIM-shell (00:MF/ADF.USIM/DF.5GS/EF.SUCI_Calc_Info)> read_binary_decoded 83 | missing Protection Scheme Identifier List data object tag 84 | 9000: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -> {} 85 | 86 | The following JSON config defines the testfile from `TS 31.121 `__ Section 4.9.4 with 87 | test keys from `TS 33.501 `__ Annex C.4. Highest priority (``0``) has a 88 | Profile-B (``identifier: 2``) key in key slot ``1``, which means the key 89 | with ``hnet_pubkey_identifier: 27``. 90 | 91 | .. code:: json 92 | 93 | { 94 | "prot_scheme_id_list": [ 95 | {"priority": 0, "identifier": 2, "key_index": 1}, 96 | {"priority": 1, "identifier": 1, "key_index": 2}, 97 | {"priority": 2, "identifier": 0, "key_index": 0}], 98 | "hnet_pubkey_list": [ 99 | {"hnet_pubkey_identifier": 27, 100 | "hnet_pubkey": "0272DA71976234CE833A6907425867B82E074D44EF907DFB4B3E21C1C2256EBCD1"}, 101 | {"hnet_pubkey_identifier": 30, 102 | "hnet_pubkey": "5A8D38864820197C3394B92613B20B91633CBD897119273BF8E4A6F4EEC0A650"}] 103 | } 104 | 105 | Write the config to file (must be single-line input as for now): 106 | 107 | :: 108 | 109 | pySIM-shell (00:MF/ADF.USIM/DF.5GS/EF.SUCI_Calc_Info)> update_binary_decoded '{ "prot_scheme_id_list": [ {"priority": 0, "identifier": 2, "key_index": 1}, {"priority": 1, "identifier": 1, "key_index": 2}, {"priority": 2, "identifier": 0, "key_index": 0}], "hnet_pubkey_list": [ {"hnet_pubkey_identifier": 27, "hnet_pubkey": "0272DA71976234CE833A6907425867B82E074D44EF907DFB4B3E21C1C2256EBCD1"}, {"hnet_pubkey_identifier": 30, "hnet_pubkey": "5A8D38864820197C3394B92613B20B91633CBD897119273BF8E4A6F4EEC0A650"}]}' 110 | 111 | WARNING: These are TEST KEYS with publicly known/specified private keys, and hence unsafe for live/secure 112 | deployments! For use in production networks, you need to generate your own set[s] of keys. 113 | 114 | Routing Indicator 115 | ----------------- 116 | 117 | The Routing Indicator must be present for the SUCI feature. By default, 118 | the contents of the file is **invalid** (ffffffff): 119 | 120 | :: 121 | 122 | pySIM-shell (00:MF)> select MF 123 | pySIM-shell (00:MF)> select ADF.USIM 124 | pySIM-shell (00:MF/ADF.USIM)> select DF.5GS 125 | pySIM-shell (00:MF/ADF.USIM/DF.5GS)> select EF.Routing_Indicator 126 | pySIM-shell (00:MF/ADF.USIM/DF.5GS/EF.Routing_Indicator)> read_binary_decoded 127 | 9000: ffffffff -> {'raw': 'ffffffff'} 128 | 129 | The Routing Indicator is a four-byte file but the actual Routing 130 | Indicator goes into bytes 0 and 1 (the other bytes are reserved). To set 131 | the Routing Indicator to 0x71: 132 | 133 | :: 134 | 135 | pySIM-shell (00:MF/ADF.USIM/DF.5GS/EF.Routing_Indicator)> update_binary 17ffffff 136 | 137 | You can also set the routing indicator to **0x0**, which is *valid* and 138 | means “routing indicator not specified”, leaving it to the modem. 139 | 140 | USIM Service Table 141 | ------------------ 142 | 143 | First, check out the USIM Service Table (UST): 144 | 145 | :: 146 | 147 | pySIM-shell (00:MF)> select MF 148 | pySIM-shell (00:MF)> select ADF.USIM 149 | pySIM-shell (00:MF/ADF.USIM)> select EF.UST 150 | pySIM-shell (00:MF/ADF.USIM/EF.UST)> read_binary_decoded 151 | 9000: beff9f9de73e0408400170730000002e00000000 -> [2, 3, 4, 5, 6, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 25, 27, 28, 29, 33, 34, 35, 38, 39, 42, 43, 44, 45, 46, 51, 60, 71, 73, 85, 86, 87, 89, 90, 93, 94, 95, 122, 123, 124, 126] 152 | 153 | .. list-table:: From TS31.102 154 | :widths: 15 40 155 | :header-rows: 1 156 | 157 | * - Service No. 158 | - Description 159 | * - 122 160 | - 5GS Mobility Management Information 161 | * - 123 162 | - 5G Security Parameters 163 | * - 124 164 | - Subscription identifier privacy support 165 | * - 125 166 | - SUCI calculation by the USIM 167 | * - 126 168 | - UAC Access Identities support 169 | * - 129 170 | - 5GS Operator PLMN List 171 | 172 | If you’d like to enable/disable any UST service: 173 | 174 | :: 175 | 176 | pySIM-shell (00:MF/ADF.USIM/EF.UST)> ust_service_deactivate 124 177 | pySIM-shell (00:MF/ADF.USIM/EF.UST)> ust_service_activate 124 178 | pySIM-shell (00:MF/ADF.USIM/EF.UST)> ust_service_deactivate 125 179 | 180 | In this case, UST Service 124 is already enabled and you’re good to go. The 181 | sysmoISIM-SJA2 does not support on-SIM calculation, so service 125 must 182 | be disabled. 183 | 184 | USIM Error with 5G and sysmoISIM 185 | -------------------------------- 186 | 187 | sysmoISIMs come 5GS-enabled. By default however, the configuration stored 188 | in the card file-system is **not valid** for 5G networks: Service 124 is enabled, 189 | but EF.SUCI_Calc_Info and EF.Routing_Indicator are empty files (hence 190 | do not contain valid data). 191 | 192 | At least for Qualcomm’s X55 modem, this results in an USIM error and the 193 | whole modem shutting 5G down. If you don’t need SUCI concealment but the 194 | smartphone refuses to connect to any 5G network, try to disable the UST 195 | service 124. 196 | -------------------------------------------------------------------------------- /docs/trace.rst: -------------------------------------------------------------------------------- 1 | pySim-trace 2 | =========== 3 | 4 | pySim-trace is a utility for high-level decode of APDU protocol traces such as those obtained with 5 | `Osmocom SIMtrace2 `_ or `osmo-qcdiag `_. 6 | 7 | pySim-trace leverages the existing knowledge of pySim-shell on anything related to SIM cards, 8 | including the structure/encoding of the various files on SIM/USIM/ISIM/HPSIM cards, and applies this 9 | to decoding protocol traces. This means that it shows not only the name of the command (like READ 10 | BINARY), but actually understands what the currently selected file is, and how to decode the 11 | contents of that file. 12 | 13 | pySim-trace also understands the parameters passed to commands and how to decode them, for example 14 | of the AUTHENTICATE command within the USIM/ISIM/HPSIM application. 15 | 16 | 17 | Demo 18 | ---- 19 | 20 | To get an idea how pySim-trace usage looks like, you can watch the relevant part of the 11/2022 21 | SIMtrace2 tutorial whose `recording is freely accessible `_. 22 | 23 | 24 | Running pySim-trace 25 | ------------------- 26 | 27 | Running pySim-trace requires you to specify the *source* of the to-be-decoded APDUs. There are several 28 | supported options, each with their own respective parameters (like a file name for PCAP decoding). 29 | 30 | See the detailed command line reference below for details. 31 | 32 | A typical execution of pySim-trace for doing live decodes of *GSMTAP (SIM APDU)* e.g. from SIMtrace2 or 33 | osmo-qcdiag would look like this: 34 | 35 | :: 36 | 37 | ./pySim-trace.py gsmtap-udp 38 | 39 | This binds to the default UDP port 4729 (GSMTAP) on localhost (127.0.0.1), and decodes any APDUs received 40 | there. 41 | 42 | 43 | 44 | pySim-trace command line reference 45 | ---------------------------------- 46 | 47 | .. argparse:: 48 | :module: pySim-trace 49 | :func: option_parser 50 | :prog: pySim-trace.py 51 | 52 | 53 | Constraints 54 | ----------- 55 | 56 | * In order to properly track the current location in the filesystem tree and other state, it is 57 | important that the trace you're decoding includes all of the communication with the SIM, ideally 58 | from the very start (power up). 59 | 60 | * pySim-trace currently only supports ETSI UICC (USIM/ISIM/HPSIM) and doesn't yet support legacy GSM 61 | SIM. This is not a fundamental technical constraint, it's just simply that nobody got around 62 | developing and testing that part. Contributions are most welcome. 63 | 64 | 65 | -------------------------------------------------------------------------------- /pySim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simula/pysim/174fd32f17b7147724933cc3d8e33ebbb9541831/pySim/__init__.py -------------------------------------------------------------------------------- /pySim/apdu/global_platform.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """APDU definition/decoder of GlobalPLatform Card Spec (currently 2.1.1) 3 | 4 | (C) 2022 by Harald Welte 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from pySim.apdu import ApduCommand, ApduCommandSet 21 | 22 | class GpDelete(ApduCommand, n='DELETE', ins=0xE4, cla=['8X', 'CX', 'EX']): 23 | _apdu_case = 4 24 | 25 | class GpStoreData(ApduCommand, n='STORE DATA', ins=0xE2, cla=['8X', 'CX', 'EX']): 26 | @classmethod 27 | def _get_apdu_case(cls, hdr:bytes) -> int: 28 | p1 = hdr[2] 29 | if p1 & 0x01: 30 | return 4 31 | else: 32 | return 3 33 | 34 | class GpGetDataCA(ApduCommand, n='GET DATA', ins=0xCA, cla=['8X', 'CX', 'EX']): 35 | _apdu_case = 4 36 | 37 | class GpGetDataCB(ApduCommand, n='GET DATA', ins=0xCB, cla=['8X', 'CX', 'EX']): 38 | _apdu_case = 4 39 | 40 | class GpGetStatus(ApduCommand, n='GET STATUS', ins=0xF2, cla=['8X', 'CX', 'EX']): 41 | _apdu_case = 4 42 | 43 | class GpInstall(ApduCommand, n='INSTALL', ins=0xE6, cla=['8X', 'CX', 'EX']): 44 | _apdu_case = 4 45 | 46 | class GpLoad(ApduCommand, n='LOAD', ins=0xE8, cla=['8X', 'CX', 'EX']): 47 | _apdu_case = 4 48 | 49 | class GpPutKey(ApduCommand, n='PUT KEY', ins=0xD8, cla=['8X', 'CX', 'EX']): 50 | _apdu_case = 4 51 | 52 | class GpSetStatus(ApduCommand, n='SET STATUS', ins=0xF0, cla=['8X', 'CX', 'EX']): 53 | _apdu_case = 3 54 | 55 | ApduCommands = ApduCommandSet('GlobalPlatform v2.3.1', cmds=[GpDelete, GpStoreData, 56 | GpGetDataCA, GpGetDataCB, GpGetStatus, GpInstall, 57 | GpLoad, GpPutKey, GpSetStatus]) 58 | -------------------------------------------------------------------------------- /pySim/apdu/ts_31_102.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # without this, pylint will fail when inner classes are used 4 | # within the 'nested' kwarg of our TlvMeta metaclass on python 3.7 :( 5 | # pylint: disable=undefined-variable 6 | 7 | """ 8 | APDU commands of 3GPP TS 31.102 V16.6.0 9 | """ 10 | from typing import Dict 11 | 12 | from construct import * 13 | from construct import Optional as COptional 14 | from pySim.filesystem import * 15 | from pySim.construct import * 16 | from pySim.ts_31_102 import SUCI_TlvDataObject 17 | 18 | from pySim.apdu import ApduCommand, ApduCommandSet 19 | 20 | # Copyright (C) 2022 Harald Welte 21 | # 22 | # This program is free software: you can redistribute it and/or modify 23 | # it under the terms of the GNU General Public License as published by 24 | # the Free Software Foundation, either version 2 of the License, or 25 | # (at your option) any later version. 26 | # 27 | # This program is distributed in the hope that it will be useful, 28 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | # GNU General Public License for more details. 31 | # 32 | # You should have received a copy of the GNU General Public License 33 | # along with this program. If not, see . 34 | # 35 | 36 | # Mapping between USIM Service Number and its description 37 | 38 | from pySim.apdu import ApduCommand, ApduCommandSet 39 | 40 | # TS 31.102 Section 7.1 41 | class UsimAuthenticateEven(ApduCommand, n='AUTHENTICATE', ins=0x88, cla=['0X', '4X', '6X']): 42 | _apdu_case = 4 43 | _construct_p2 = BitStruct('scope'/Enum(Flag, mf=0, df_adf_specific=1), 44 | BitsInteger(4), 45 | 'authentication_context'/Enum(BitsInteger(3), gsm=0, umts=1, 46 | vgcs_vbs=2, gba=4)) 47 | _cs_cmd_gsm_3g = Struct('_rand_len'/Int8ub, 'rand'/HexAdapter(Bytes(this._rand_len)), 48 | '_autn_len'/COptional(Int8ub), 'autn'/If(this._autn_len, HexAdapter(Bytes(this._autn_len)))) 49 | _cs_cmd_vgcs = Struct('_vsid_len'/Int8ub, 'vservice_id'/HexAdapter(Bytes(this._vsid_len)), 50 | '_vkid_len'/Int8ub, 'vk_id'/HexAdapter(Bytes(this._vkid_len)), 51 | '_vstk_rand_len'/Int8ub, 'vstk_rand'/HexAdapter(Bytes(this._vstk_rand_len))) 52 | _cmd_gba_bs = Struct('_rand_len'/Int8ub, 'rand'/HexAdapter(Bytes(this._rand_len)), 53 | '_autn_len'/Int8ub, 'autn'/HexAdapter(Bytes(this._autn_len))) 54 | _cmd_gba_naf = Struct('_naf_id_len'/Int8ub, 'naf_id'/HexAdapter(Bytes(this._naf_id_len)), 55 | '_impi_len'/Int8ub, 'impi'/HexAdapter(Bytes(this._impi_len))) 56 | _cs_cmd_gba = Struct('tag'/Int8ub, 'body'/Switch(this.tag, { 0xDD: 'bootstrap'/_cmd_gba_bs, 57 | 0xDE: 'naf_derivation'/_cmd_gba_naf })) 58 | _cs_rsp_gsm = Struct('_len_sres'/Int8ub, 'sres'/HexAdapter(Bytes(this._len_sres)), 59 | '_len_kc'/Int8ub, 'kc'/HexAdapter(Bytes(this._len_kc))) 60 | _rsp_3g_ok = Struct('_len_res'/Int8ub, 'res'/HexAdapter(Bytes(this._len_res)), 61 | '_len_ck'/Int8ub, 'ck'/HexAdapter(Bytes(this._len_ck)), 62 | '_len_ik'/Int8ub, 'ik'/HexAdapter(Bytes(this._len_ik)), 63 | '_len_kc'/COptional(Int8ub), 'kc'/If(this._len_kc, HexAdapter(Bytes(this._len_kc)))) 64 | _rsp_3g_sync = Struct('_len_auts'/Int8ub, 'auts'/HexAdapter(Bytes(this._len_auts))) 65 | _cs_rsp_3g = Struct('tag'/Int8ub, 'body'/Switch(this.tag, { 0xDB: 'success'/_rsp_3g_ok, 66 | 0xDC: 'sync_fail'/_rsp_3g_sync})) 67 | _cs_rsp_vgcs = Struct(Const(b'\xDB'), '_vstk_len'/Int8ub, 'vstk'/HexAdapter(Bytes(this._vstk_len))) 68 | _cs_rsp_gba_naf = Struct(Const(b'\xDB'), '_ks_ext_naf_len'/Int8ub, 'ks_ext_naf'/HexAdapter(Bytes(this._ks_ext_naf_len))) 69 | def _decode_cmd(self) -> Dict: 70 | r = {} 71 | r['p1'] = parse_construct(self._construct_p1, self.p1.to_bytes(1, 'big')) 72 | r['p2'] = parse_construct(self._construct_p2, self.p2.to_bytes(1, 'big')) 73 | auth_ctx = r['p2']['authentication_context'] 74 | if auth_ctx in ['gsm', 'umts']: 75 | r['body'] = parse_construct(self._cs_cmd_gsm_3g, self.cmd_data) 76 | elif auth_ctx == 'vgcs_vbs': 77 | r['body'] = parse_construct(self._cs_cmd_vgcs, self.cmd_data) 78 | elif auth_ctx == 'gba': 79 | r['body'] = parse_construct(self._cs_cmd_gba, self.cmd_data) 80 | else: 81 | raise ValueError('Unsupported authentication_context: %s' % auth_ctx) 82 | return r 83 | 84 | def _decode_rsp(self) -> Dict: 85 | r = {} 86 | auth_ctx = self.cmd_dict['p2']['authentication_context'] 87 | if auth_ctx == 'gsm': 88 | r['body'] = parse_construct(self._cs_rsp_gsm, self.rsp_data) 89 | elif auth_ctx == 'umts': 90 | r['body'] = parse_construct(self._cs_rsp_3g, self.rsp_data) 91 | elif auth_ctx == 'vgcs_vbs': 92 | r['body'] = parse_construct(self._cs_rsp_vgcs, self.rsp_data) 93 | elif auth_ctx == 'gba': 94 | if self.cmd_dict['body']['tag'] == 0xDD: 95 | r['body'] = parse_construct(self._cs_rsp_3g, self.rsp_data) 96 | else: 97 | r['body'] = parse_construct(self._cs_rsp_gba_naf, self.rsp_data) 98 | else: 99 | raise ValueError('Unsupported authentication_context: %s' % auth_ctx) 100 | return r 101 | 102 | class UsimAuthenticateOdd(ApduCommand, n='AUTHENTICATE', ins=0x89, cla=['0X', '4X', '6X']): 103 | _apdu_case = 4 104 | _construct_p2 = BitStruct('scope'/Enum(Flag, mf=0, df_adf_specific=1), 105 | BitsInteger(4), 106 | 'authentication_context'/Enum(BitsInteger(3), mbms=5, local_key=6)) 107 | # TS 31.102 Section 7.5 108 | class UsimGetIdentity(ApduCommand, n='GET IDENTITY', ins=0x78, cla=['8X', 'CX', 'EX']): 109 | _apdu_case = 4 110 | _construct_p2 = BitStruct('scope'/Enum(Flag, mf=0, df_adf_specific=1), 111 | 'identity_context'/Enum(BitsInteger(7), suci=1, suci_5g_nswo=2)) 112 | _tlv_rsp = SUCI_TlvDataObject 113 | 114 | ApduCommands = ApduCommandSet('TS 31.102', cmds=[UsimAuthenticateEven, UsimAuthenticateOdd, 115 | UsimGetIdentity]) 116 | -------------------------------------------------------------------------------- /pySim/apdu_source/__init__.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import logging 3 | from typing import Union 4 | from pySim.apdu import Apdu, Tpdu, CardReset, TpduFilter 5 | 6 | PacketType = Union[Apdu, Tpdu, CardReset] 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | class ApduSource(abc.ABC): 11 | def __init__(self): 12 | self.apdu_filter = TpduFilter(None) 13 | 14 | @abc.abstractmethod 15 | def read_packet(self) -> PacketType: 16 | """Read one packet from the source.""" 17 | pass 18 | 19 | def read(self) -> Union[Apdu, CardReset]: 20 | """Main function to call by the user: Blocking read, returns Apdu or CardReset.""" 21 | apdu = None 22 | # loop until we actually have an APDU to return 23 | while not apdu: 24 | r = self.read_packet() 25 | if not r: 26 | continue 27 | if isinstance(r, Tpdu): 28 | apdu = self.apdu_filter.input_tpdu(r) 29 | elif isinstance(r, Apdu): 30 | apdu = r 31 | elif isinstance(r, CardReset): 32 | apdu = r 33 | else: 34 | ValueError('Unknown read_packet() return %s' % r) 35 | return apdu 36 | -------------------------------------------------------------------------------- /pySim/apdu_source/gsmtap.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # (C) 2022 by Harald Welte 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | from pySim.gsmtap import GsmtapMessage, GsmtapSource 20 | from . import ApduSource, PacketType, CardReset 21 | 22 | from pySim.apdu.ts_102_221 import ApduCommands as UiccApduCommands 23 | from pySim.apdu.ts_31_102 import ApduCommands as UsimApduCommands 24 | from pySim.apdu.global_platform import ApduCommands as GpApduCommands 25 | ApduCommands = UiccApduCommands + UsimApduCommands + GpApduCommands 26 | 27 | class GsmtapApduSource(ApduSource): 28 | """ApduSource for handling GSMTAP-SIM messages received via UDP, such as 29 | those generated by simtrace2-sniff. Note that *if* you use IP loopback 30 | and localhost addresses (which is the default), you will need to start 31 | this source before starting simtrace2-sniff, as otherwise the latter will 32 | claim the GSMTAP UDP port. 33 | """ 34 | def __init__(self, bind_ip:str='127.0.0.1', bind_port:int=4729): 35 | """Create a UDP socket for receiving GSMTAP-SIM messages. 36 | Args: 37 | bind_ip: IP address to which the socket should be bound (default: 127.0.0.1) 38 | bind_port: UDP port number to which the socket should be bound (default: 4729) 39 | """ 40 | super().__init__() 41 | self.gsmtap = GsmtapSource(bind_ip, bind_port) 42 | 43 | def read_packet(self) -> PacketType: 44 | gsmtap_msg, addr = self.gsmtap.read_packet() 45 | if gsmtap_msg['type'] != 'sim': 46 | raise ValueError('Unsupported GSMTAP type %s' % gsmtap_msg['type']) 47 | sub_type = gsmtap_msg['sub_type'] 48 | if sub_type == 'apdu': 49 | return ApduCommands.parse_cmd_bytes(gsmtap_msg['body']) 50 | elif sub_type == 'atr': 51 | # card has been reset 52 | return CardReset(gsmtap_msg['body']) 53 | elif sub_type in ['pps_req', 'pps_rsp']: 54 | # simply ignore for now 55 | pass 56 | else: 57 | raise ValueError('Unsupported GSMTAP-SIM sub-type %s' % sub_type) 58 | -------------------------------------------------------------------------------- /pySim/apdu_source/pyshark_gsmtap.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # (C) 2022 by Harald Welte 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | import sys 20 | import logging 21 | from pprint import pprint as pp 22 | from typing import Tuple 23 | import pyshark 24 | 25 | from pySim.utils import h2b, b2h 26 | from pySim.apdu import Tpdu 27 | from pySim.gsmtap import GsmtapMessage 28 | from . import ApduSource, PacketType, CardReset 29 | 30 | from pySim.apdu.ts_102_221 import ApduCommands as UiccApduCommands 31 | from pySim.apdu.ts_31_102 import ApduCommands as UsimApduCommands 32 | from pySim.apdu.global_platform import ApduCommands as GpApduCommands 33 | ApduCommands = UiccApduCommands + UsimApduCommands + GpApduCommands 34 | 35 | logger = logging.getLogger(__name__) 36 | 37 | class _PysharkGsmtap(ApduSource): 38 | """APDU Source [provider] base class for reading GSMTAP SIM APDU via tshark.""" 39 | 40 | def __init__(self, pyshark_inst): 41 | self.pyshark = pyshark_inst 42 | self.bank_id = None 43 | self.bank_slot = None 44 | self.cmd_tpdu = None 45 | super().__init__() 46 | 47 | def read_packet(self) -> PacketType: 48 | p = self.pyshark.next() 49 | return self._parse_packet(p) 50 | 51 | def _set_or_verify_bank_slot(self, bsl: Tuple[int, int]): 52 | """Keep track of the bank:slot to make sure we don't mix traces of multiple cards""" 53 | if not self.bank_id: 54 | self.bank_id = bsl[0] 55 | self.bank_slot = bsl[1] 56 | else: 57 | if self.bank_id != bsl[0] or self.bank_slot != bsl[1]: 58 | raise ValueError('Received data for unexpected B(%u:%u)' % (bsl[0], bsl[1])) 59 | 60 | def _parse_packet(self, p) -> PacketType: 61 | udp_layer = p['udp'] 62 | udp_payload_hex = udp_layer.get_field('payload').replace(':','') 63 | gsmtap = GsmtapMessage(h2b(udp_payload_hex)) 64 | gsmtap_msg = gsmtap.decode() 65 | if gsmtap_msg['type'] != 'sim': 66 | raise ValueError('Unsupported GSMTAP type %s' % gsmtap_msg['type']) 67 | sub_type = gsmtap_msg['sub_type'] 68 | if sub_type == 'apdu': 69 | return ApduCommands.parse_cmd_bytes(gsmtap_msg['body']) 70 | elif sub_type == 'atr': 71 | # card has been reset 72 | return CardReset(gsmtap_msg['body']) 73 | elif sub_type in ['pps_req', 'pps_rsp']: 74 | # simply ignore for now 75 | pass 76 | else: 77 | raise ValueError('Unsupported GSMTAP-SIM sub-type %s' % sub_type) 78 | 79 | class PysharkGsmtapPcap(_PysharkGsmtap): 80 | """APDU Source [provider] class for reading GSMTAP from a PCAP 81 | file via pyshark, which in turn uses tshark (part of wireshark). 82 | """ 83 | def __init__(self, pcap_filename): 84 | """ 85 | Args: 86 | pcap_filename: File name of the pcap file to be opened 87 | """ 88 | pyshark_inst = pyshark.FileCapture(pcap_filename, display_filter='gsm_sim', use_json=True, keep_packets=False) 89 | super().__init__(pyshark_inst) 90 | 91 | -------------------------------------------------------------------------------- /pySim/apdu_source/pyshark_rspro.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | # (C) 2022 by Harald Welte 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | import sys 20 | import logging 21 | from pprint import pprint as pp 22 | from typing import Tuple 23 | import pyshark 24 | 25 | from pySim.utils import h2b, b2h 26 | from pySim.apdu import Tpdu 27 | from . import ApduSource, PacketType, CardReset 28 | 29 | logger = logging.getLogger(__name__) 30 | 31 | class _PysharkRspro(ApduSource): 32 | """APDU Source [provider] base class for reading RSPRO (osmo-remsim) via tshark.""" 33 | 34 | def __init__(self, pyshark_inst): 35 | self.pyshark = pyshark_inst 36 | self.bank_id = None 37 | self.bank_slot = None 38 | self.cmd_tpdu = None 39 | super().__init__() 40 | 41 | @staticmethod 42 | def get_bank_slot(bank_slot) -> Tuple[int, int]: 43 | """Convert a 'bankSlot_element' field into a tuple of bank_id, slot_nr""" 44 | bank_id = bank_slot.get_field('bankId') 45 | slot_nr = bank_slot.get_field('slotNr') 46 | return int(bank_id), int(slot_nr) 47 | 48 | @staticmethod 49 | def get_client_slot(client_slot) -> Tuple[int, int]: 50 | """Convert a 'clientSlot_element' field into a tuple of client_id, slot_nr""" 51 | client_id = client_slot.get_field('clientId') 52 | slot_nr = client_slot.get_field('slotNr') 53 | return int(client_id), int(slot_nr) 54 | 55 | @staticmethod 56 | def get_pstatus(pstatus) -> Tuple[int, int, int]: 57 | """Convert a 'slotPhysStatus_element' field into a tuple of vcc, reset, clk""" 58 | vccPresent = int(pstatus.get_field('vccPresent')) 59 | resetActive = int(pstatus.get_field('resetActive')) 60 | clkActive = int(pstatus.get_field('clkActive')) 61 | return vccPresent, resetActive, clkActive 62 | 63 | def read_packet(self) -> PacketType: 64 | p = self.pyshark.next() 65 | return self._parse_packet(p) 66 | 67 | def _set_or_verify_bank_slot(self, bsl: Tuple[int, int]): 68 | """Keep track of the bank:slot to make sure we don't mix traces of multiple cards""" 69 | if not self.bank_id: 70 | self.bank_id = bsl[0] 71 | self.bank_slot = bsl[1] 72 | else: 73 | if self.bank_id != bsl[0] or self.bank_slot != bsl[1]: 74 | raise ValueError('Received data for unexpected B(%u:%u)' % (bsl[0], bsl[1])) 75 | 76 | def _parse_packet(self, p) -> PacketType: 77 | rspro_layer = p['rspro'] 78 | #print("Layer: %s" % rspro_layer) 79 | rspro_element = rspro_layer.get_field('RsproPDU_element') 80 | #print("Element: %s" % rspro_element) 81 | msg_type = rspro_element.get_field('msg') 82 | rspro_msg = rspro_element.get_field('msg_tree') 83 | if msg_type == '12': # tpduModemToCard 84 | modem2card = rspro_msg.get_field('tpduModemToCard_element') 85 | #print(modem2card) 86 | client_slot = modem2card.get_field('fromClientSlot_element') 87 | csl = self.get_client_slot(client_slot) 88 | bank_slot = modem2card.get_field('toBankSlot_element') 89 | bsl = self.get_bank_slot(bank_slot) 90 | self._set_or_verify_bank_slot(bsl) 91 | data = modem2card.get_field('data').replace(':','') 92 | logger.debug("C(%u:%u) -> B(%u:%u): %s" % (csl[0], csl[1], bsl[0], bsl[1], data)) 93 | # store the CMD portion until the RSP portion arrives later 94 | self.cmd_tpdu = h2b(data) 95 | elif msg_type == '13': # tpduCardToModem 96 | card2modem = rspro_msg.get_field('tpduCardToModem_element') 97 | #print(card2modem) 98 | client_slot = card2modem.get_field('toClientSlot_element') 99 | csl = self.get_client_slot(client_slot) 100 | bank_slot = card2modem.get_field('fromBankSlot_element') 101 | bsl = self.get_bank_slot(bank_slot) 102 | self._set_or_verify_bank_slot(bsl) 103 | data = card2modem.get_field('data').replace(':','') 104 | logger.debug("C(%u:%u) <- B(%u:%u): %s" % (csl[0], csl[1], bsl[0], bsl[1], data)) 105 | rsp_tpdu = h2b(data) 106 | if self.cmd_tpdu: 107 | # combine this R-TPDU with the C-TPDU we saw earlier 108 | r = Tpdu(self.cmd_tpdu, rsp_tpdu) 109 | self.cmd_tpdu = False 110 | return r 111 | elif msg_type == '14': # clientSlotStatus 112 | cl_slotstatus = rspro_msg.get_field('clientSlotStatusInd_element') 113 | #print(cl_slotstatus) 114 | client_slot = cl_slotstatus.get_field('fromClientSlot_element') 115 | bank_slot = cl_slotstatus.get_field('toBankSlot_element') 116 | slot_pstatus = cl_slotstatus.get_field('slotPhysStatus_element') 117 | vccPresent, resetActive, clkActive = self.get_pstatus(slot_pstatus) 118 | if vccPresent and clkActive and not resetActive: 119 | logger.debug("RESET") 120 | #TODO: extract ATR from RSPRO message and use it here 121 | return CardReset(None) 122 | else: 123 | print("Unhandled msg type %s: %s" % (msg_type, rspro_msg)) 124 | 125 | 126 | class PysharkRsproPcap(_PysharkRspro): 127 | """APDU Source [provider] class for reading RSPRO (osmo-remsim) from a PCAP 128 | file via pyshark, which in turn uses tshark (part of wireshark). 129 | 130 | In order to use this, you need a wireshark patched with RSPRO support, 131 | such as can be found at https://gitea.osmocom.org/osmocom/wireshark/src/branch/laforge/rspro 132 | 133 | A STANDARD UPSTREAM WIRESHARK *DOES NOT WORK*. 134 | """ 135 | def __init__(self, pcap_filename): 136 | """ 137 | Args: 138 | pcap_filename: File name of the pcap file to be opened 139 | """ 140 | pyshark_inst = pyshark.FileCapture(pcap_filename, display_filter='rspro', use_json=True, keep_packets=False) 141 | super().__init__(pyshark_inst) 142 | 143 | class PysharkRsproLive(_PysharkRspro): 144 | """APDU Source [provider] class for reading RSPRO (osmo-remsim) from a live capture 145 | via pyshark, which in turn uses tshark (part of wireshark). 146 | 147 | In order to use this, you need a wireshark patched with RSPRO support, 148 | such as can be found at https://gitea.osmocom.org/osmocom/wireshark/src/branch/laforge/rspro 149 | 150 | A STANDARD UPSTREAM WIRESHARK *DOES NOT WORK*. 151 | """ 152 | def __init__(self, interface, bpf_filter='tcp port 9999 or tcp port 9998'): 153 | """ 154 | Args: 155 | interface: Network interface name to capture packets on (like "eth0") 156 | bfp_filter: libpcap capture filter to use 157 | """ 158 | pyshark_inst = pyshark.LiveCapture(interface=interface, display_filter='rspro', bpf_filter=bpf_filter, 159 | use_json=True) 160 | super().__init__(pyshark_inst) 161 | -------------------------------------------------------------------------------- /pySim/app.py: -------------------------------------------------------------------------------- 1 | # (C) 2021-2023 by Harald Welte 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | 17 | import inspect 18 | from typing import Tuple 19 | 20 | from pySim.transport import LinkBase 21 | from pySim.commands import SimCardCommands 22 | from pySim.filesystem import CardModel, CardApplication 23 | from pySim.cards import card_detect, SimCardBase, UiccCardBase 24 | from pySim.exceptions import NoCardError 25 | from pySim.runtime import RuntimeState 26 | from pySim.profile import CardProfile 27 | from pySim.cdma_ruim import CardProfileRUIM 28 | from pySim.ts_102_221 import CardProfileUICC 29 | 30 | # we need to import this module so that the SysmocomSJA2 sub-class of 31 | # CardModel is created, which will add the ATR-based matching and 32 | # calling of SysmocomSJA2.add_files. See CardModel.apply_matching_models 33 | import pySim.sysmocom_sja2 34 | 35 | # we need to import these modules so that the various sub-classes of 36 | # CardProfile are created, which will be used in init_card() to iterate 37 | # over all known CardProfile sub-classes. 38 | import pySim.ts_31_102 39 | import pySim.ts_31_103 40 | import pySim.ts_31_104 41 | import pySim.ara_m 42 | import pySim.global_platform 43 | import pySim.euicc 44 | 45 | def init_card(sl: LinkBase) -> Tuple[RuntimeState, SimCardBase]: 46 | """ 47 | Detect card in reader and setup card profile and runtime state. This 48 | function must be called at least once on startup. The card and runtime 49 | state object (rs) is required for all pySim-shell commands. 50 | """ 51 | 52 | # Create command layer 53 | scc = SimCardCommands(transport=sl) 54 | 55 | # Wait up to three seconds for a card in reader and try to detect 56 | # the card type. 57 | print("Waiting for card...") 58 | sl.wait_for_card(3) 59 | 60 | generic_card = False 61 | card = card_detect(scc) 62 | if card is None: 63 | print("Warning: Could not detect card type - assuming a generic card type...") 64 | card = SimCardBase(scc) 65 | generic_card = True 66 | 67 | profile = CardProfile.pick(scc) 68 | if profile is None: 69 | # It is not an unrecoverable error in case profile detection fails. It 70 | # just means that pySim was unable to recognize the card profile. This 71 | # may happen in particular with unprovisioned cards that do not have 72 | # any files on them yet. 73 | print("Unsupported card type!") 74 | return None, card 75 | 76 | # ETSI TS 102 221, Table 9.3 specifies a default for the PIN key 77 | # references, however card manufactures may still decide to pick an 78 | # arbitrary key reference. In case we run on a generic card class that is 79 | # detected as an UICC, we will pick the key reference that is officially 80 | # specified. 81 | if generic_card and isinstance(profile, CardProfileUICC): 82 | card._adm_chv_num = 0x0A 83 | 84 | print("Info: Card is of type: %s" % str(profile)) 85 | 86 | # FIXME: this shouldn't really be here but somewhere else/more generic. 87 | # We cannot do it within pySim/profile.py as that would create circular 88 | # dependencies between the individual profiles and profile.py. 89 | if isinstance(profile, CardProfileUICC): 90 | for app_cls in CardApplication.__subclasses__(): 91 | constr_sig = inspect.signature(app_cls.__init__) 92 | # skip any intermediary sub-classes such as CardApplicationSD 93 | if len(constr_sig.parameters) != 1: 94 | continue 95 | profile.add_application(app_cls()) 96 | # We have chosen SimCard() above, but we now know it actually is an UICC 97 | # so it's safe to assume it supports USIM application (which we're adding above). 98 | # IF we don't do this, we will have a SimCard but try USIM specific commands like 99 | # the update_ust method (see https://osmocom.org/issues/6055) 100 | if generic_card: 101 | card = UiccCardBase(scc) 102 | 103 | # Create runtime state with card profile 104 | rs = RuntimeState(card, profile) 105 | 106 | CardModel.apply_matching_models(scc, rs) 107 | 108 | # inform the transport that we can do context-specific SW interpretation 109 | sl.set_sw_interpreter(rs) 110 | 111 | return rs, card 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /pySim/card_handler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ pySim: card handler utilities. A 'card handler' is some method 4 | by which cards can be inserted/removed into the card reader. For 5 | normal smart card readers, this has to be done manually. However, 6 | there are also automatic card feeders. 7 | """ 8 | 9 | # 10 | # (C) 2019 by Sysmocom s.f.m.c. GmbH 11 | # All Rights Reserved 12 | # 13 | # This program is free software: you can redistribute it and/or modify 14 | # it under the terms of the GNU General Public License as published by 15 | # the Free Software Foundation, either version 2 of the License, or 16 | # (at your option) any later version. 17 | # 18 | # This program is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with this program. If not, see . 25 | # 26 | 27 | from pySim.transport import LinkBase 28 | 29 | import subprocess 30 | import sys 31 | import yaml 32 | 33 | 34 | class CardHandlerBase: 35 | """Abstract base class representing a mechanism for card insertion/removal.""" 36 | 37 | def __init__(self, sl: LinkBase): 38 | self.sl = sl 39 | 40 | def get(self, first: bool = False): 41 | """Method called when pySim needs a new card to be inserted. 42 | 43 | Args: 44 | first : set to true when the get method is called the 45 | first time. This is required to prevent blocking 46 | when a card is already inserted into the reader. 47 | The reader API would not recognize that card as 48 | "new card" until it would be removed and re-inserted 49 | again. 50 | """ 51 | print("Ready for Programming: ", end='') 52 | self._get(first) 53 | 54 | def error(self): 55 | """Method called when pySim failed to program a card. Move card to 'bad' batch.""" 56 | print("Programming failed: ", end='') 57 | self._error() 58 | 59 | def done(self): 60 | """Method called when pySim failed to program a card. Move card to 'good' batch.""" 61 | print("Programming successful: ", end='') 62 | self._done() 63 | 64 | def _get(self, first: bool = False): 65 | pass 66 | 67 | def _error(self): 68 | pass 69 | 70 | def _done(self): 71 | pass 72 | 73 | 74 | class CardHandler(CardHandlerBase): 75 | """Manual card handler: User is prompted to insert/remove card from the reader.""" 76 | 77 | def _get(self, first: bool = False): 78 | print("Insert card now (or CTRL-C to cancel)") 79 | self.sl.wait_for_card(newcardonly=not first) 80 | 81 | def _error(self): 82 | print("Remove card from reader") 83 | print("") 84 | 85 | def _done(self): 86 | print("Remove card from reader") 87 | print("") 88 | 89 | 90 | class CardHandlerAuto(CardHandlerBase): 91 | """Automatic card handler: A machine is used to handle the cards.""" 92 | 93 | verbose = True 94 | 95 | def __init__(self, sl: LinkBase, config_file: str): 96 | super().__init__(sl) 97 | print("Card handler Config-file: " + str(config_file)) 98 | with open(config_file) as cfg: 99 | self.cmds = yaml.load(cfg, Loader=yaml.FullLoader) 100 | self.verbose = (self.cmds.get('verbose') == True) 101 | 102 | def __print_outout(self, out): 103 | print("") 104 | print("Card handler output:") 105 | print("---------------------8<---------------------") 106 | stdout = out[0].strip() 107 | if len(stdout) > 0: 108 | print("stdout:") 109 | print(stdout) 110 | stderr = out[1].strip() 111 | if len(stderr) > 0: 112 | print("stderr:") 113 | print(stderr) 114 | print("---------------------8<---------------------") 115 | print("") 116 | 117 | def __exec_cmd(self, command): 118 | print("Card handler Commandline: " + str(command)) 119 | 120 | proc = subprocess.Popen( 121 | [command], stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) 122 | out = proc.communicate() 123 | rc = proc.returncode 124 | 125 | if rc != 0 or self.verbose: 126 | self.__print_outout(out) 127 | 128 | if rc != 0: 129 | print("") 130 | print("Error: Card handler failure! (rc=" + str(rc) + ")") 131 | sys.exit(rc) 132 | 133 | def _get(self, first: bool = False): 134 | print("Transporting card into the reader-bay...") 135 | self.__exec_cmd(self.cmds['get']) 136 | if self.sl: 137 | self.sl.connect() 138 | 139 | def _error(self): 140 | print("Transporting card to the error-bin...") 141 | self.__exec_cmd(self.cmds['error']) 142 | print("") 143 | 144 | def _done(self): 145 | print("Transporting card into the collector bin...") 146 | self.__exec_cmd(self.cmds['done']) 147 | print("") 148 | -------------------------------------------------------------------------------- /pySim/card_key_provider.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Obtaining card parameters (mostly key data) from external source. 3 | 4 | This module contains a base class and a concrete implementation of 5 | obtaining card key material (or other card-individual parameters) from 6 | an external data source. 7 | 8 | This is used e.g. to keep PIN/PUK data in some file on disk, avoiding 9 | the need of manually entering the related card-individual data on every 10 | operation with pySim-shell. 11 | """ 12 | 13 | # (C) 2021 by Sysmocom s.f.m.c. GmbH 14 | # All Rights Reserved 15 | # 16 | # Author: Philipp Maier 17 | # 18 | # This program is free software: you can redistribute it and/or modify 19 | # it under the terms of the GNU General Public License as published by 20 | # the Free Software Foundation, either version 2 of the License, or 21 | # (at your option) any later version. 22 | # 23 | # This program is distributed in the hope that it will be useful, 24 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 25 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 26 | # GNU General Public License for more details. 27 | # 28 | # You should have received a copy of the GNU General Public License 29 | # along with this program. If not, see . 30 | 31 | from typing import List, Dict, Optional 32 | 33 | import abc 34 | import csv 35 | 36 | card_key_providers = [] # type: List['CardKeyProvider'] 37 | 38 | 39 | class CardKeyProvider(abc.ABC): 40 | """Base class, not containing any concrete implementation.""" 41 | 42 | VALID_FIELD_NAMES = ['ICCID', 'ADM1', 43 | 'IMSI', 'PIN1', 'PIN2', 'PUK1', 'PUK2'] 44 | 45 | # check input parameters, but do nothing concrete yet 46 | def _verify_get_data(self, fields: List[str] = [], key: str = 'ICCID', value: str = "") -> Dict[str, str]: 47 | """Verify multiple fields for identified card. 48 | 49 | Args: 50 | fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained 51 | key : look-up key to identify card data, such as 'ICCID' 52 | value : value for look-up key to identify card data 53 | Returns: 54 | dictionary of {field, value} strings for each requested field from 'fields' 55 | """ 56 | for f in fields: 57 | if (f not in self.VALID_FIELD_NAMES): 58 | raise ValueError("Requested field name '%s' is not a valid field name, valid field names are: %s" % 59 | (f, str(self.VALID_FIELD_NAMES))) 60 | 61 | if (key not in self.VALID_FIELD_NAMES): 62 | raise ValueError("Key field name '%s' is not a valid field name, valid field names are: %s" % 63 | (key, str(self.VALID_FIELD_NAMES))) 64 | 65 | return {} 66 | 67 | def get_field(self, field: str, key: str = 'ICCID', value: str = "") -> Optional[str]: 68 | """get a single field from CSV file using a specified key/value pair""" 69 | fields = [field] 70 | result = self.get(fields, key, value) 71 | return result.get(field) 72 | 73 | @abc.abstractmethod 74 | def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]: 75 | """Get multiple card-individual fields for identified card. 76 | 77 | Args: 78 | fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained 79 | key : look-up key to identify card data, such as 'ICCID' 80 | value : value for look-up key to identify card data 81 | Returns: 82 | dictionary of {field, value} strings for each requested field from 'fields' 83 | """ 84 | 85 | 86 | class CardKeyProviderCsv(CardKeyProvider): 87 | """Card key provider implementation that allows to query against a specified CSV file""" 88 | csv_file = None 89 | filename = None 90 | 91 | def __init__(self, filename: str): 92 | """ 93 | Args: 94 | filename : file name (path) of CSV file containing card-individual key/data 95 | """ 96 | self.csv_file = open(filename, 'r') 97 | if not self.csv_file: 98 | raise RuntimeError("Could not open CSV file '%s'" % filename) 99 | self.filename = filename 100 | 101 | def get(self, fields: List[str], key: str, value: str) -> Dict[str, str]: 102 | super()._verify_get_data(fields, key, value) 103 | 104 | self.csv_file.seek(0) 105 | cr = csv.DictReader(self.csv_file) 106 | if not cr: 107 | raise RuntimeError( 108 | "Could not open DictReader for CSV-File '%s'" % self.filename) 109 | cr.fieldnames = [field.upper() for field in cr.fieldnames] 110 | 111 | rc = {} 112 | for row in cr: 113 | if row[key] == value: 114 | for f in fields: 115 | if f in row: 116 | rc.update({f: row[f]}) 117 | else: 118 | raise RuntimeError("CSV-File '%s' lacks column '%s'" % 119 | (self.filename, f)) 120 | return rc 121 | 122 | 123 | def card_key_provider_register(provider: CardKeyProvider, provider_list=card_key_providers): 124 | """Register a new card key provider. 125 | 126 | Args: 127 | provider : the to-be-registered provider 128 | provider_list : override the list of providers from the global default 129 | """ 130 | if not isinstance(provider, CardKeyProvider): 131 | raise ValueError("provider is not a card data provier") 132 | provider_list.append(provider) 133 | 134 | 135 | def card_key_provider_get(fields, key: str, value: str, provider_list=card_key_providers) -> Dict[str, str]: 136 | """Query all registered card data providers for card-individual [key] data. 137 | 138 | Args: 139 | fields : list of valid field names such as 'ADM1', 'PIN1', ... which are to be obtained 140 | key : look-up key to identify card data, such as 'ICCID' 141 | value : value for look-up key to identify card data 142 | provider_list : override the list of providers from the global default 143 | Returns: 144 | dictionary of {field, value} strings for each requested field from 'fields' 145 | """ 146 | for p in provider_list: 147 | if not isinstance(p, CardKeyProvider): 148 | raise ValueError( 149 | "provider list contains element which is not a card data provier") 150 | result = p.get(fields, key, value) 151 | if result: 152 | return result 153 | return {} 154 | 155 | 156 | def card_key_provider_get_field(field: str, key: str, value: str, provider_list=card_key_providers) -> Optional[str]: 157 | """Query all registered card data providers for a single field. 158 | 159 | Args: 160 | field : name valid field such as 'ADM1', 'PIN1', ... which is to be obtained 161 | key : look-up key to identify card data, such as 'ICCID' 162 | value : value for look-up key to identify card data 163 | provider_list : override the list of providers from the global default 164 | Returns: 165 | dictionary of {field, value} strings for the requested field 166 | """ 167 | for p in provider_list: 168 | if not isinstance(p, CardKeyProvider): 169 | raise ValueError( 170 | "provider list contains element which is not a card data provier") 171 | result = p.get_field(field, key, value) 172 | if result: 173 | return result 174 | return None 175 | -------------------------------------------------------------------------------- /pySim/cards.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ pySim: Card programmation logic 4 | """ 5 | 6 | # 7 | # Copyright (C) 2009-2010 Sylvain Munaut 8 | # Copyright (C) 2011-2023 Harald Welte 9 | # Copyright (C) 2017 Alexander.Chemeris 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see . 23 | # 24 | 25 | from typing import Optional, Dict, Tuple 26 | from pySim.ts_102_221 import EF_DIR 27 | from pySim.ts_51_011 import DF_GSM 28 | import abc 29 | 30 | from pySim.utils import * 31 | from pySim.commands import Path, SimCardCommands 32 | 33 | class CardBase: 34 | """General base class for some kind of telecommunications card.""" 35 | def __init__(self, scc: SimCardCommands): 36 | self._scc = scc 37 | self._aids = [] 38 | 39 | def reset(self) -> Optional[Hexstr]: 40 | rc = self._scc.reset_card() 41 | if rc == 1: 42 | return self._scc.get_atr() 43 | else: 44 | return None 45 | 46 | def set_apdu_parameter(self, cla: Hexstr, sel_ctrl: Hexstr) -> None: 47 | """Set apdu parameters (class byte and selection control bytes)""" 48 | self._scc.cla_byte = cla 49 | self._scc.sel_ctrl = sel_ctrl 50 | 51 | def get_apdu_parameter(self) -> Tuple[Hexstr, Hexstr]: 52 | """Get apdu parameters (class byte and selection control bytes)""" 53 | return (self._scc.cla_byte, self._scc.sel_ctrl) 54 | 55 | def erase(self): 56 | print("warning: erasing is not supported for specified card type!") 57 | return 58 | 59 | def file_exists(self, fid: Path) -> bool: 60 | res_arr = self._scc.try_select_path(fid) 61 | for res in res_arr: 62 | if res[1] != '9000': 63 | return False 64 | return True 65 | 66 | def read_aids(self) -> List[Hexstr]: 67 | # a non-UICC doesn't have any applications. Convenience helper to avoid 68 | # callers having to do hasattr('read_aids') ahead of every call. 69 | return [] 70 | 71 | 72 | class SimCardBase(CardBase): 73 | """Here we only add methods for commands specified in TS 51.011, without 74 | any higher-layer processing.""" 75 | name = 'SIM' 76 | 77 | def __init__(self, scc: SimCardCommands): 78 | super(SimCardBase, self).__init__(scc) 79 | self._scc.cla_byte = "A0" 80 | self._scc.sel_ctrl = "0000" 81 | 82 | def probe(self) -> bool: 83 | df_gsm = DF_GSM() 84 | return self.file_exists(df_gsm.fid) 85 | 86 | 87 | class UiccCardBase(SimCardBase): 88 | name = 'UICC' 89 | 90 | def __init__(self, scc: SimCardCommands): 91 | super(UiccCardBase, self).__init__(scc) 92 | self._scc.cla_byte = "00" 93 | self._scc.sel_ctrl = "0004" # request an FCP 94 | # See also: ETSI TS 102 221, Table 9.3 95 | self._adm_chv_num = 0x0A 96 | 97 | def probe(self) -> bool: 98 | # EF.DIR is a mandatory EF on all ICCIDs; however it *may* also exist on a TS 51.011 SIM 99 | ef_dir = EF_DIR() 100 | return self.file_exists(ef_dir.fid) 101 | 102 | def read_aids(self) -> List[Hexstr]: 103 | """Fetch all the AIDs present on UICC""" 104 | self._aids = [] 105 | try: 106 | ef_dir = EF_DIR() 107 | # Find out how many records the EF.DIR has 108 | # and store all the AIDs in the UICC 109 | rec_cnt = self._scc.record_count(ef_dir.fid) 110 | for i in range(0, rec_cnt): 111 | rec = self._scc.read_record(ef_dir.fid, i + 1) 112 | if (rec[0][0:2], rec[0][4:6]) == ('61', '4f') and len(rec[0]) > 12 \ 113 | and rec[0][8:8 + int(rec[0][6:8], 16) * 2] not in self._aids: 114 | self._aids.append(rec[0][8:8 + int(rec[0][6:8], 16) * 2]) 115 | except Exception as e: 116 | print("Can't read AIDs from SIM -- %s" % (str(e),)) 117 | self._aids = [] 118 | return self._aids 119 | 120 | @staticmethod 121 | def _get_aid(adf="usim") -> Optional[Hexstr]: 122 | aid_map = {} 123 | # First (known) halves of the U/ISIM AID 124 | aid_map["usim"] = "a0000000871002" 125 | aid_map["isim"] = "a0000000871004" 126 | adf = adf.lower() 127 | if adf in aid_map: 128 | return aid_map[adf] 129 | return None 130 | 131 | def _complete_aid(self, aid: Hexstr) -> Optional[Hexstr]: 132 | """find the complete version of an ADF.U/ISIM AID""" 133 | # Find full AID by partial AID: 134 | if is_hex(aid): 135 | for aid_known in self._aids: 136 | if len(aid_known) >= len(aid) and aid == aid_known[0:len(aid)]: 137 | return aid_known 138 | return None 139 | 140 | def adf_present(self, adf: str = "usim") -> bool: 141 | """Check if the AID of the specified ADF is present in EF.DIR (call read_aids before use)""" 142 | aid = self._get_aid(adf) 143 | if aid: 144 | aid_full = self._complete_aid(aid) 145 | if aid_full: 146 | return True 147 | return False 148 | 149 | def select_adf_by_aid(self, adf: str = "usim", scc: Optional[SimCardCommands] = None) -> Tuple[Optional[Hexstr], Optional[SwHexstr]]: 150 | """Select ADF.U/ISIM in the Card using its full AID""" 151 | # caller may pass a custom scc; we fall back to default 152 | scc = scc or self._scc 153 | if is_hex(adf): 154 | aid = adf 155 | else: 156 | aid = self._get_aid(adf) 157 | if aid: 158 | aid_full = self._complete_aid(aid) 159 | if aid_full: 160 | return scc.select_adf(aid_full) 161 | else: 162 | # If we cannot get the full AID, try with short AID 163 | return scc.select_adf(aid) 164 | return (None, None) 165 | 166 | def card_detect(scc: SimCardCommands) -> Optional[CardBase]: 167 | # UICC always has higher preference, as a UICC might also contain a SIM application 168 | uicc = UiccCardBase(scc) 169 | if uicc.probe(): 170 | return uicc 171 | 172 | # this is for detecting a real, classic TS 11.11 SIM card without any UICC support 173 | sim = SimCardBase(scc) 174 | if sim.probe(): 175 | return sim 176 | 177 | return None 178 | -------------------------------------------------------------------------------- /pySim/cdma_ruim.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """R-UIM (Removable User Identity Module) card profile (see 3GPP2 C.S0023-D) 3 | 4 | (C) 2023 by Vadim Yanitskiy 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | import enum 21 | 22 | from pySim.utils import * 23 | from pySim.filesystem import * 24 | from pySim.profile import match_ruim 25 | from pySim.profile import CardProfile, CardProfileAddon 26 | from pySim.ts_51_011 import CardProfileSIM 27 | from pySim.ts_51_011 import DF_TELECOM, DF_GSM 28 | from pySim.ts_51_011 import EF_ServiceTable 29 | from pySim.construct import * 30 | from construct import * 31 | 32 | 33 | # Mapping between CDMA Service Number and its description 34 | EF_CST_map = { 35 | 1 : 'CHV disable function', 36 | 2 : 'Abbreviated Dialing Numbers (ADN)', 37 | 3 : 'Fixed Dialing Numbers (FDN)', 38 | 4 : 'Short Message Storage (SMS)', 39 | 5 : 'HRPD', 40 | 6 : 'Enhanced Phone Book', 41 | 7 : 'Multi Media Domain (MMD)', 42 | 8 : 'SF_EUIMID-based EUIMID', 43 | 9 : 'MEID Support', 44 | 10 : 'Extension1', 45 | 11 : 'Extension2', 46 | 12 : 'SMS Parameters', 47 | 13 : 'Last Number Dialled (LND)', 48 | 14 : 'Service Category Program for BC-SMS', 49 | 15 : 'Messaging and 3GPD Extensions', 50 | 16 : 'Root Certificates', 51 | 17 : 'CDMA Home Service Provider Name', 52 | 18 : 'Service Dialing Numbers (SDN)', 53 | 19 : 'Extension3', 54 | 20 : '3GPD-SIP', 55 | 21 : 'WAP Browser', 56 | 22 : 'Java', 57 | 23 : 'Reserved for CDG', 58 | 24 : 'Reserved for CDG', 59 | 25 : 'Data Download via SMS Broadcast', 60 | 26 : 'Data Download via SMS-PP', 61 | 27 : 'Menu Selection', 62 | 28 : 'Call Control', 63 | 29 : 'Proactive R-UIM', 64 | 30 : 'AKA', 65 | 31 : 'IPv6', 66 | 32 : 'RFU', 67 | 33 : 'RFU', 68 | 34 : 'RFU', 69 | 35 : 'RFU', 70 | 36 : 'RFU', 71 | 37 : 'RFU', 72 | 38 : '3GPD-MIP', 73 | 39 : 'BCMCS', 74 | 40 : 'Multimedia Messaging Service (MMS)', 75 | 41 : 'Extension 8', 76 | 42 : 'MMS User Connectivity Parameters', 77 | 43 : 'Application Authentication', 78 | 44 : 'Group Identifier Level 1', 79 | 45 : 'Group Identifier Level 2', 80 | 46 : 'De-Personalization Control Keys', 81 | 47 : 'Cooperative Network List', 82 | } 83 | 84 | 85 | ###################################################################### 86 | # DF.CDMA 87 | ###################################################################### 88 | 89 | class EF_SPN(TransparentEF): 90 | '''3.4.31 CDMA Home Service Provider Name''' 91 | 92 | _test_de_encode = [ 93 | ( "010801536b796c696e6b204e57ffffffffffffffffffffffffffffffffffffffffffff", 94 | { 'rfu1' : 0, 'show_in_hsa' : True, 'rfu2' : 0, 95 | 'char_encoding' : 8, 'lang_ind' : 1, 'spn' : 'Skylink NW' } ), 96 | ] 97 | 98 | def __init__(self, fid='6f41', sfid=None, name='EF.SPN', 99 | desc='Service Provider Name', size=(35, 35), **kwargs): 100 | super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs) 101 | self._construct = BitStruct( 102 | # Byte 1: Display Condition 103 | 'rfu1'/BitsRFU(7), 104 | 'show_in_hsa'/Flag, 105 | # Byte 2: Character Encoding 106 | 'rfu2'/BitsRFU(3), 107 | 'char_encoding'/BitsInteger(5), # see C.R1001-G 108 | # Byte 3: Language Indicator 109 | 'lang_ind'/BitsInteger(8), # see C.R1001-G 110 | # Bytes 4-35: Service Provider Name 111 | 'spn'/Bytewise(GsmString(32)) 112 | ) 113 | 114 | class EF_AD(TransparentEF): 115 | '''3.4.33 Administrative Data''' 116 | 117 | _test_de_encode = [ 118 | ( "000000", { 'ms_operation_mode' : 'normal', 'additional_info' : '0000', 'rfu' : '' } ), 119 | ] 120 | 121 | class OP_MODE(enum.IntEnum): 122 | normal = 0x00 123 | type_approval = 0x80 124 | normal_and_specific_facilities = 0x01 125 | type_approval_and_specific_facilities = 0x81 126 | maintenance_off_line = 0x02 127 | cell_test = 0x04 128 | 129 | def __init__(self, fid='6f43', sfid=None, name='EF.AD', 130 | desc='Service Provider Name', size=(3, None), **kwargs): 131 | super().__init__(fid, sfid=sfid, name=name, desc=desc, size=size, **kwargs) 132 | self._construct = Struct( 133 | # Byte 1: Display Condition 134 | 'ms_operation_mode'/Enum(Byte, self.OP_MODE), 135 | # Bytes 2-3: Additional information 136 | 'additional_info'/HexAdapter(Bytes(2)), 137 | # Bytes 4..: RFU 138 | 'rfu'/HexAdapter(GreedyBytesRFU), 139 | ) 140 | 141 | 142 | class EF_SMS(LinFixedEF): 143 | '''3.4.27 Short Messages''' 144 | def __init__(self, fid='6f3c', sfid=None, name='EF.SMS', desc='Short messages', **kwargs): 145 | super().__init__(fid, sfid=sfid, name=name, desc=desc, rec_len=(2, 255), **kwargs) 146 | self._construct = Struct( 147 | # Byte 1: Status 148 | 'status'/BitStruct( 149 | 'rfu87'/BitsRFU(2), 150 | 'protection'/Flag, 151 | 'rfu54'/BitsRFU(2), 152 | 'status'/FlagsEnum(BitsInteger(2), read=0, to_be_read=1, sent=2, to_be_sent=3), 153 | 'used'/Flag, 154 | ), 155 | # Byte 2: Length 156 | 'length'/Int8ub, 157 | # Bytes 3..: SMS Transport Layer Message 158 | 'tpdu'/Bytes(lambda ctx: ctx.length if ctx.status.used else 0), 159 | ) 160 | 161 | 162 | class DF_CDMA(CardDF): 163 | def __init__(self): 164 | super().__init__(fid='7f25', name='DF.CDMA', 165 | desc='CDMA related files (3GPP2 C.S0023-D)') 166 | files = [ 167 | # TODO: lots of other files 168 | EF_ServiceTable('6f32', None, 'EF.CST', 169 | 'CDMA Service Table', table=EF_CST_map, size=(5, 16)), 170 | EF_SPN(), 171 | EF_AD(), 172 | EF_SMS(), 173 | ] 174 | self.add_files(files) 175 | 176 | 177 | class CardProfileRUIM(CardProfile): 178 | '''R-UIM card profile as per 3GPP2 C.S0023-D''' 179 | 180 | ORDER = 2 181 | 182 | def __init__(self): 183 | super().__init__('R-UIM', desc='CDMA R-UIM Card', cla="a0", 184 | sel_ctrl="0000", files_in_mf=[DF_TELECOM(), DF_GSM(), DF_CDMA()]) 185 | 186 | @staticmethod 187 | def decode_select_response(resp_hex: str) -> object: 188 | # TODO: Response parameters/data in case of DF_CDMA (section 2.6) 189 | return CardProfileSIM.decode_select_response(resp_hex) 190 | 191 | @staticmethod 192 | def match_with_card(scc: SimCardCommands) -> bool: 193 | return match_ruim(scc) 194 | 195 | class AddonRUIM(CardProfileAddon): 196 | """An Addon that can be found on on a combined SIM + RUIM or UICC + RUIM to support CDMA.""" 197 | def __init__(self): 198 | files = [ 199 | DF_CDMA() 200 | ] 201 | super().__init__('RUIM', desc='CDMA RUIM', files_in_mf=files) 202 | 203 | def probe(self, card: 'CardBase') -> bool: 204 | return card.file_exists(self.files_in_mf[0].fid) 205 | -------------------------------------------------------------------------------- /pySim/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ pySim: Exceptions 4 | """ 5 | 6 | # 7 | # Copyright (C) 2009-2010 Sylvain Munaut 8 | # Copyright (C) 2021 Harald Welte 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | 24 | 25 | class NoCardError(Exception): 26 | """No card was found in the reader.""" 27 | pass 28 | 29 | 30 | class ProtocolError(Exception): 31 | """Some kind of protocol level error interfacing with the card.""" 32 | pass 33 | 34 | 35 | class ReaderError(Exception): 36 | """Some kind of general error with the card reader.""" 37 | pass 38 | 39 | 40 | class SwMatchError(Exception): 41 | """Raised when an operation specifies an expected SW but the actual SW from 42 | the card doesn't match.""" 43 | 44 | def __init__(self, sw_actual: str, sw_expected: str, rs=None): 45 | """ 46 | Args: 47 | sw_actual : the SW we actually received from the card (4 hex digits) 48 | sw_expected : the SW we expected to receive from the card (4 hex digits) 49 | rs : interpreter class to convert SW to string 50 | """ 51 | self.sw_actual = sw_actual 52 | self.sw_expected = sw_expected 53 | self.rs = rs 54 | 55 | def __str__(self): 56 | if self.rs and self.rs.lchan[0]: 57 | r = self.rs.lchan[0].interpret_sw(self.sw_actual) 58 | if r: 59 | return "SW match failed! Expected %s and got %s: %s - %s" % (self.sw_expected, self.sw_actual, r[0], r[1]) 60 | return "SW match failed! Expected %s and got %s." % (self.sw_expected, self.sw_actual) 61 | -------------------------------------------------------------------------------- /pySim/iso7816_4.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """Utilities / Functions related to ISO 7816-4 3 | 4 | (C) 2022 by Harald Welte 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 2 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see . 18 | """ 19 | 20 | from construct import * 21 | from pySim.construct import * 22 | from pySim.utils import * 23 | from pySim.filesystem import * 24 | from pySim.tlv import * 25 | 26 | # Table 91 + Section 8.2.1.2 27 | class ApplicationId(BER_TLV_IE, tag=0x4f): 28 | _construct = GreedyBytes 29 | 30 | # Table 91 31 | class ApplicationLabel(BER_TLV_IE, tag=0x50): 32 | _construct = GreedyBytes 33 | 34 | # Table 91 + Section 5.3.1.2 35 | class FileReference(BER_TLV_IE, tag=0x51): 36 | _construct = GreedyBytes 37 | 38 | # Table 91 39 | class CommandApdu(BER_TLV_IE, tag=0x52): 40 | _construct = GreedyBytes 41 | 42 | # Table 91 43 | class DiscretionaryData(BER_TLV_IE, tag=0x53): 44 | _construct = GreedyBytes 45 | 46 | # Table 91 47 | class DiscretionaryTemplate(BER_TLV_IE, tag=0x73): 48 | _construct = GreedyBytes 49 | 50 | # Table 91 + RFC1738 / RFC2396 51 | class URL(BER_TLV_IE, tag=0x5f50): 52 | _construct = GreedyString('ascii') 53 | 54 | # Table 91 55 | class ApplicationRelatedDOSet(BER_TLV_IE, tag=0x61): 56 | _construct = GreedyBytes 57 | 58 | # Section 8.2.1.3 Application Template 59 | class ApplicationTemplate(BER_TLV_IE, tag=0x61, nested=[ApplicationId, ApplicationLabel, FileReference, 60 | CommandApdu, DiscretionaryData, DiscretionaryTemplate, URL, 61 | ApplicationRelatedDOSet]): 62 | pass 63 | -------------------------------------------------------------------------------- /pySim/jsonpath.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import json 3 | import pprint 4 | import jsonpath_ng 5 | 6 | """JSONpath utility functions as needed within pysim. 7 | 8 | As pySim-sell has the ability to represent SIM files as JSON strings, 9 | adding JSONpath allows us to conveniently modify individual sub-fields 10 | of a file or record in its JSON representation. 11 | """ 12 | 13 | # (C) 2021 by Harald Welte 14 | # 15 | # This program is free software: you can redistribute it and/or modify 16 | # it under the terms of the GNU General Public License as published by 17 | # the Free Software Foundation, either version 2 of the License, or 18 | # (at your option) any later version. 19 | # 20 | # This program is distributed in the hope that it will be useful, 21 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | # GNU General Public License for more details. 24 | # 25 | # You should have received a copy of the GNU General Public License 26 | # along with this program. If not, see . 27 | 28 | 29 | def js_path_find(js_dict, js_path): 30 | """Find/Match a JSON path within a given JSON-serializable dict. 31 | Args: 32 | js_dict : JSON-serializable dict to operate on 33 | js_path : JSONpath string 34 | Returns: Result of the JSONpath expression 35 | """ 36 | jsonpath_expr = jsonpath_ng.parse(js_path) 37 | return jsonpath_expr.find(js_dict) 38 | 39 | 40 | def js_path_modify(js_dict, js_path, new_val): 41 | """Find/Match a JSON path within a given JSON-serializable dict. 42 | Args: 43 | js_dict : JSON-serializable dict to operate on 44 | js_path : JSONpath string 45 | new_val : New value for field in js_dict at js_path 46 | """ 47 | jsonpath_expr = jsonpath_ng.parse(js_path) 48 | jsonpath_expr.find(js_dict) 49 | jsonpath_expr.update(js_dict, new_val) 50 | -------------------------------------------------------------------------------- /pySim/legacy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simula/pysim/174fd32f17b7147724933cc3d8e33ebbb9541831/pySim/legacy/__init__.py -------------------------------------------------------------------------------- /pySim/legacy/ts_31_102.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Various constants from 3GPP TS 31.102 V17.9.0 usd by *legacy* code 5 | """ 6 | 7 | # 8 | # Copyright (C) 2020 Supreeth Herle 9 | # Copyright (C) 2021-2023 Harald Welte 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 2 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see . 23 | 24 | 25 | EF_USIM_ADF_map = { 26 | 'LI': '6F05', 27 | 'ARR': '6F06', 28 | 'IMSI': '6F07', 29 | 'Keys': '6F08', 30 | 'KeysPS': '6F09', 31 | 'DCK': '6F2C', 32 | 'HPPLMN': '6F31', 33 | 'CNL': '6F32', 34 | 'ACMmax': '6F37', 35 | 'UST': '6F38', 36 | 'ACM': '6F39', 37 | 'FDN': '6F3B', 38 | 'SMS': '6F3C', 39 | 'GID1': '6F3E', 40 | 'GID2': '6F3F', 41 | 'MSISDN': '6F40', 42 | 'PUCT': '6F41', 43 | 'SMSP': '6F42', 44 | 'SMSS': '6F42', 45 | 'CBMI': '6F45', 46 | 'SPN': '6F46', 47 | 'SMSR': '6F47', 48 | 'CBMID': '6F48', 49 | 'SDN': '6F49', 50 | 'EXT2': '6F4B', 51 | 'EXT3': '6F4C', 52 | 'BDN': '6F4D', 53 | 'EXT5': '6F4E', 54 | 'CCP2': '6F4F', 55 | 'CBMIR': '6F50', 56 | 'EXT4': '6F55', 57 | 'EST': '6F56', 58 | 'ACL': '6F57', 59 | 'CMI': '6F58', 60 | 'START-HFN': '6F5B', 61 | 'THRESHOLD': '6F5C', 62 | 'PLMNwAcT': '6F60', 63 | 'OPLMNwAcT': '6F61', 64 | 'HPLMNwAcT': '6F62', 65 | 'PSLOCI': '6F73', 66 | 'ACC': '6F78', 67 | 'FPLMN': '6F7B', 68 | 'LOCI': '6F7E', 69 | 'ICI': '6F80', 70 | 'OCI': '6F81', 71 | 'ICT': '6F82', 72 | 'OCT': '6F83', 73 | 'AD': '6FAD', 74 | 'VGCS': '6FB1', 75 | 'VGCSS': '6FB2', 76 | 'VBS': '6FB3', 77 | 'VBSS': '6FB4', 78 | 'eMLPP': '6FB5', 79 | 'AAeM': '6FB6', 80 | 'ECC': '6FB7', 81 | 'Hiddenkey': '6FC3', 82 | 'NETPAR': '6FC4', 83 | 'PNN': '6FC5', 84 | 'OPL': '6FC6', 85 | 'MBDN': '6FC7', 86 | 'EXT6': '6FC8', 87 | 'MBI': '6FC9', 88 | 'MWIS': '6FCA', 89 | 'CFIS': '6FCB', 90 | 'EXT7': '6FCC', 91 | 'SPDI': '6FCD', 92 | 'MMSN': '6FCE', 93 | 'EXT8': '6FCF', 94 | 'MMSICP': '6FD0', 95 | 'MMSUP': '6FD1', 96 | 'MMSUCP': '6FD2', 97 | 'NIA': '6FD3', 98 | 'VGCSCA': '6FD4', 99 | 'VBSCA': '6FD5', 100 | 'GBAP': '6FD6', 101 | 'MSK': '6FD7', 102 | 'MUK': '6FD8', 103 | 'EHPLMN': '6FD9', 104 | 'GBANL': '6FDA', 105 | 'EHPLMNPI': '6FDB', 106 | 'LRPLMNSI': '6FDC', 107 | 'NAFKCA': '6FDD', 108 | 'SPNI': '6FDE', 109 | 'PNNI': '6FDF', 110 | 'NCP-IP': '6FE2', 111 | 'EPSLOCI': '6FE3', 112 | 'EPSNSC': '6FE4', 113 | 'UFC': '6FE6', 114 | 'UICCIARI': '6FE7', 115 | 'NASCONFIG': '6FE8', 116 | 'PWC': '6FEC', 117 | 'FDNURI': '6FED', 118 | 'BDNURI': '6FEE', 119 | 'SDNURI': '6FEF', 120 | 'IWL': '6FF0', 121 | 'IPS': '6FF1', 122 | 'IPD': '6FF2', 123 | 'ePDGId': '6FF3', 124 | 'ePDGSelection': '6FF4', 125 | 'ePDGIdEm': '6FF5', 126 | 'ePDGSelectionEm': '6FF6', 127 | } 128 | 129 | LOCI_STATUS_map = { 130 | 0: 'updated', 131 | 1: 'not updated', 132 | 2: 'plmn not allowed', 133 | 3: 'locatation area not allowed' 134 | } 135 | -------------------------------------------------------------------------------- /pySim/legacy/ts_31_103.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Various constants from 3GPP TS 31.103 V16.1.0 used by *legacy* code only 5 | """ 6 | 7 | # Copyright (C) 2020 Supreeth Herle 8 | # Copyright (C) 2021 Harald Welte 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | EF_ISIM_ADF_map = { 24 | 'IST': '6F07', 25 | 'IMPI': '6F02', 26 | 'DOMAIN': '6F03', 27 | 'IMPU': '6F04', 28 | 'AD': '6FAD', 29 | 'ARR': '6F06', 30 | 'PCSCF': '6F09', 31 | 'GBAP': '6FD5', 32 | 'GBANL': '6FD7', 33 | 'NAFKCA': '6FDD', 34 | 'UICCIARI': '6FE7', 35 | 'SMS': '6F3C', 36 | 'SMSS': '6F43', 37 | 'SMSR': '6F47', 38 | 'SMSP': '6F42', 39 | 'FromPreferred': '6FF7', 40 | 'IMSConfigData': '6FF8', 41 | 'XCAPConfigData': '6FFC', 42 | 'WebRTCURI': '6FFA' 43 | } 44 | -------------------------------------------------------------------------------- /pySim/legacy/ts_51_011.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ Various constants from 3GPP TS 51.011 used by *legacy* code only. 4 | 5 | This will likely be removed in future versions of pySim. Please instead 6 | use the pySim.filesystem class model with the various profile/application 7 | specific extension modules. 8 | """ 9 | 10 | # Copyright (C) 2017 Alexander.Chemeris 11 | # Copyright (C) 2021 Harald Welte 12 | # 13 | # This program is free software: you can redistribute it and/or modify 14 | # it under the terms of the GNU General Public License as published by 15 | # the Free Software Foundation, either version 2 of the License, or 16 | # (at your option) any later version. 17 | # 18 | # This program is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with this program. If not, see . 25 | 26 | 27 | MF_num = '3F00' 28 | 29 | DF_num = { 30 | 'TELECOM': '7F10', 31 | 32 | 'GSM': '7F20', 33 | 'IS-41': '7F22', 34 | 'FP-CTS': '7F23', 35 | 36 | 'GRAPHICS': '5F50', 37 | 38 | 'IRIDIUM': '5F30', 39 | 'GLOBST': '5F31', 40 | 'ICO': '5F32', 41 | 'ACeS': '5F33', 42 | 43 | 'EIA/TIA-553': '5F40', 44 | 'CTS': '5F60', 45 | 'SOLSA': '5F70', 46 | 47 | 'MExE': '5F3C', 48 | } 49 | 50 | EF_num = { 51 | # MF 52 | 'ICCID': '2FE2', 53 | 'ELP': '2F05', 54 | 'DIR': '2F00', 55 | 56 | # DF_TELECOM 57 | 'ADN': '6F3A', 58 | 'FDN': '6F3B', 59 | 'SMS': '6F3C', 60 | 'CCP': '6F3D', 61 | 'MSISDN': '6F40', 62 | 'SMSP': '6F42', 63 | 'SMSS': '6F43', 64 | 'LND': '6F44', 65 | 'SMSR': '6F47', 66 | 'SDN': '6F49', 67 | 'EXT1': '6F4A', 68 | 'EXT2': '6F4B', 69 | 'EXT3': '6F4C', 70 | 'BDN': '6F4D', 71 | 'EXT4': '6F4E', 72 | 'CMI': '6F58', 73 | 'ECCP': '6F4F', 74 | 75 | # DF_GRAPHICS 76 | 'IMG': '4F20', 77 | 78 | # DF_SoLSA 79 | 'SAI': '4F30', 80 | 'SLL': '4F31', 81 | 82 | # DF_MExE 83 | 'MExE-ST': '4F40', 84 | 'ORPK': '4F41', 85 | 'ARPK': '4F42', 86 | 'TPRPK': '4F43', 87 | 88 | # DF_GSM 89 | 'LP': '6F05', 90 | 'IMSI': '6F07', 91 | 'Kc': '6F20', 92 | 'DCK': '6F2C', 93 | 'PLMNsel': '6F30', 94 | 'HPPLMN': '6F31', 95 | 'CNL': '6F32', 96 | 'ACMmax': '6F37', 97 | 'SST': '6F38', 98 | 'ACM': '6F39', 99 | 'GID1': '6F3E', 100 | 'GID2': '6F3F', 101 | 'PUCT': '6F41', 102 | 'CBMI': '6F45', 103 | 'SPN': '6F46', 104 | 'CBMID': '6F48', 105 | 'BCCH': '6F74', 106 | 'ACC': '6F78', 107 | 'FPLMN': '6F7B', 108 | 'LOCI': '6F7E', 109 | 'AD': '6FAD', 110 | 'PHASE': '6FAE', 111 | 'VGCS': '6FB1', 112 | 'VGCSS': '6FB2', 113 | 'VBS': '6FB3', 114 | 'VBSS': '6FB4', 115 | 'eMLPP': '6FB5', 116 | 'AAeM': '6FB6', 117 | 'ECC': '6FB7', 118 | 'CBMIR': '6F50', 119 | 'NIA': '6F51', 120 | 'KcGPRS': '6F52', 121 | 'LOCIGPRS': '6F53', 122 | 'SUME': '6F54', 123 | 'PLMNwAcT': '6F60', 124 | 'OPLMNwAcT': '6F61', 125 | # Figure 8 names it HPLMNAcT, but in the text it's names it HPLMNwAcT 126 | 'HPLMNAcT': '6F62', 127 | 'HPLMNwAcT': '6F62', 128 | 'CPBCCH': '6F63', 129 | 'INVSCAN': '6F64', 130 | 'PNN': '6FC5', 131 | 'OPL': '6FC6', 132 | 'MBDN': '6FC7', 133 | 'EXT6': '6FC8', 134 | 'MBI': '6FC9', 135 | 'MWIS': '6FCA', 136 | 'CFIS': '6FCB', 137 | 'EXT7': '6FCC', 138 | 'SPDI': '6FCD', 139 | 'MMSN': '6FCE', 140 | 'EXT8': '6FCF', 141 | 'MMSICP': '6FD0', 142 | 'MMSUP': '6FD1', 143 | 'MMSUCP': '6FD2', 144 | } 145 | 146 | DF = { 147 | 'TELECOM': [MF_num, DF_num['TELECOM']], 148 | 149 | 'GSM': [MF_num, DF_num['GSM']], 150 | 'IS-41': [MF_num, DF_num['IS-41']], 151 | 'FP-CTS': [MF_num, DF_num['FP-CTS']], 152 | 153 | 'GRAPHICS': [MF_num, DF_num['GRAPHICS']], 154 | 155 | 'IRIDIUM': [MF_num, DF_num['IRIDIUM']], 156 | 'GLOBST': [MF_num, DF_num['GLOBST']], 157 | 'ICO': [MF_num, DF_num['ICO']], 158 | 'ACeS': [MF_num, DF_num['ACeS']], 159 | 160 | 'EIA/TIA-553': [MF_num, DF_num['EIA/TIA-553']], 161 | 'CTS': [MF_num, DF_num['CTS']], 162 | 'SoLSA': [MF_num, DF_num['SOLSA']], 163 | 164 | 'MExE': [MF_num, DF_num['MExE']], 165 | } 166 | 167 | 168 | EF = { 169 | 'ICCID': [MF_num, EF_num['ICCID']], 170 | 'ELP': [MF_num, EF_num['ELP']], 171 | 'DIR': [MF_num, EF_num['DIR']], 172 | 173 | 'ADN': DF['TELECOM']+[EF_num['ADN']], 174 | 'FDN': DF['TELECOM']+[EF_num['FDN']], 175 | 'SMS': DF['TELECOM']+[EF_num['SMS']], 176 | 'CCP': DF['TELECOM']+[EF_num['CCP']], 177 | 'MSISDN': DF['TELECOM']+[EF_num['MSISDN']], 178 | 'SMSP': DF['TELECOM']+[EF_num['SMSP']], 179 | 'SMSS': DF['TELECOM']+[EF_num['SMSS']], 180 | 'LND': DF['TELECOM']+[EF_num['LND']], 181 | 'SMSR': DF['TELECOM']+[EF_num['SMSR']], 182 | 'SDN': DF['TELECOM']+[EF_num['SDN']], 183 | 'EXT1': DF['TELECOM']+[EF_num['EXT1']], 184 | 'EXT2': DF['TELECOM']+[EF_num['EXT2']], 185 | 'EXT3': DF['TELECOM']+[EF_num['EXT3']], 186 | 'BDN': DF['TELECOM']+[EF_num['BDN']], 187 | 'EXT4': DF['TELECOM']+[EF_num['EXT4']], 188 | 'CMI': DF['TELECOM']+[EF_num['CMI']], 189 | 'ECCP': DF['TELECOM']+[EF_num['ECCP']], 190 | 191 | 'IMG': DF['GRAPHICS']+[EF_num['IMG']], 192 | 193 | 'SAI': DF['SoLSA']+[EF_num['SAI']], 194 | 'SLL': DF['SoLSA']+[EF_num['SLL']], 195 | 196 | 'MExE-ST': DF['MExE']+[EF_num['MExE-ST']], 197 | 'ORPK': DF['MExE']+[EF_num['ORPK']], 198 | 'ARPK': DF['MExE']+[EF_num['ARPK']], 199 | 'TPRPK': DF['MExE']+[EF_num['TPRPK']], 200 | 201 | 'LP': DF['GSM']+[EF_num['LP']], 202 | 'IMSI': DF['GSM']+[EF_num['IMSI']], 203 | 'Kc': DF['GSM']+[EF_num['Kc']], 204 | 'DCK': DF['GSM']+[EF_num['DCK']], 205 | 'PLMNsel': DF['GSM']+[EF_num['PLMNsel']], 206 | 'HPPLMN': DF['GSM']+[EF_num['HPPLMN']], 207 | 'CNL': DF['GSM']+[EF_num['CNL']], 208 | 'ACMmax': DF['GSM']+[EF_num['ACMmax']], 209 | 'SST': DF['GSM']+[EF_num['SST']], 210 | 'ACM': DF['GSM']+[EF_num['ACM']], 211 | 'GID1': DF['GSM']+[EF_num['GID1']], 212 | 'GID2': DF['GSM']+[EF_num['GID2']], 213 | 'PUCT': DF['GSM']+[EF_num['PUCT']], 214 | 'CBMI': DF['GSM']+[EF_num['CBMI']], 215 | 'SPN': DF['GSM']+[EF_num['SPN']], 216 | 'CBMID': DF['GSM']+[EF_num['CBMID']], 217 | 'BCCH': DF['GSM']+[EF_num['BCCH']], 218 | 'ACC': DF['GSM']+[EF_num['ACC']], 219 | 'FPLMN': DF['GSM']+[EF_num['FPLMN']], 220 | 'LOCI': DF['GSM']+[EF_num['LOCI']], 221 | 'AD': DF['GSM']+[EF_num['AD']], 222 | 'PHASE': DF['GSM']+[EF_num['PHASE']], 223 | 'VGCS': DF['GSM']+[EF_num['VGCS']], 224 | 'VGCSS': DF['GSM']+[EF_num['VGCSS']], 225 | 'VBS': DF['GSM']+[EF_num['VBS']], 226 | 'VBSS': DF['GSM']+[EF_num['VBSS']], 227 | 'eMLPP': DF['GSM']+[EF_num['eMLPP']], 228 | 'AAeM': DF['GSM']+[EF_num['AAeM']], 229 | 'ECC': DF['GSM']+[EF_num['ECC']], 230 | 'CBMIR': DF['GSM']+[EF_num['CBMIR']], 231 | 'NIA': DF['GSM']+[EF_num['NIA']], 232 | 'KcGPRS': DF['GSM']+[EF_num['KcGPRS']], 233 | 'LOCIGPRS': DF['GSM']+[EF_num['LOCIGPRS']], 234 | 'SUME': DF['GSM']+[EF_num['SUME']], 235 | 'PLMNwAcT': DF['GSM']+[EF_num['PLMNwAcT']], 236 | 'OPLMNwAcT': DF['GSM']+[EF_num['OPLMNwAcT']], 237 | # Figure 8 names it HPLMNAcT, but in the text it's names it HPLMNwAcT 238 | 'HPLMNAcT': DF['GSM']+[EF_num['HPLMNAcT']], 239 | 'HPLMNwAcT': DF['GSM']+[EF_num['HPLMNAcT']], 240 | 'CPBCCH': DF['GSM']+[EF_num['CPBCCH']], 241 | 'INVSCAN': DF['GSM']+[EF_num['INVSCAN']], 242 | 'PNN': DF['GSM']+[EF_num['PNN']], 243 | 'OPL': DF['GSM']+[EF_num['OPL']], 244 | 'MBDN': DF['GSM']+[EF_num['MBDN']], 245 | 'EXT6': DF['GSM']+[EF_num['EXT6']], 246 | 'MBI': DF['GSM']+[EF_num['MBI']], 247 | 'MWIS': DF['GSM']+[EF_num['MWIS']], 248 | 'CFIS': DF['GSM']+[EF_num['CFIS']], 249 | 'EXT7': DF['GSM']+[EF_num['EXT7']], 250 | 'SPDI': DF['GSM']+[EF_num['SPDI']], 251 | 'MMSN': DF['GSM']+[EF_num['MMSN']], 252 | 'EXT8': DF['GSM']+[EF_num['EXT8']], 253 | 'MMSICP': DF['GSM']+[EF_num['MMSICP']], 254 | 'MMSUP': DF['GSM']+[EF_num['MMSUP']], 255 | 'MMSUCP': DF['GSM']+[EF_num['MMSUCP']], 256 | } 257 | 258 | -------------------------------------------------------------------------------- /pySim/profile.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ pySim: tell old 2G SIMs apart from UICC 4 | """ 5 | 6 | # 7 | # (C) 2021 by Sysmocom s.f.m.c. GmbH 8 | # All Rights Reserved 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | # 23 | 24 | from pySim.commands import SimCardCommands 25 | from pySim.filesystem import CardApplication, interpret_sw 26 | from pySim.utils import all_subclasses 27 | import abc 28 | import operator 29 | from typing import List 30 | 31 | 32 | def _mf_select_test(scc: SimCardCommands, 33 | cla_byte: str, sel_ctrl: str, 34 | fids: List[str]) -> bool: 35 | cla_byte_bak = scc.cla_byte 36 | sel_ctrl_bak = scc.sel_ctrl 37 | scc.reset_card() 38 | 39 | scc.cla_byte = cla_byte 40 | scc.sel_ctrl = sel_ctrl 41 | rc = True 42 | try: 43 | for fid in fids: 44 | scc.select_file(fid) 45 | except: 46 | rc = False 47 | 48 | scc.reset_card() 49 | scc.cla_byte = cla_byte_bak 50 | scc.sel_ctrl = sel_ctrl_bak 51 | return rc 52 | 53 | 54 | def match_uicc(scc: SimCardCommands) -> bool: 55 | """ Try to access MF via UICC APDUs (3GPP TS 102.221), if this works, the 56 | card is considered a UICC card. 57 | """ 58 | return _mf_select_test(scc, "00", "0004", ["3f00"]) 59 | 60 | 61 | def match_sim(scc: SimCardCommands) -> bool: 62 | """ Try to access MF via 2G APDUs (3GPP TS 11.11), if this works, the card 63 | is also a simcard. This will be the case for most UICC cards, but there may 64 | also be plain UICC cards without 2G support as well. 65 | """ 66 | return _mf_select_test(scc, "a0", "0000", ["3f00"]) 67 | 68 | 69 | def match_ruim(scc: SimCardCommands) -> bool: 70 | """ Try to access MF/DF.CDMA via 2G APDUs (3GPP TS 11.11), if this works, 71 | the card is considered an R-UIM card for CDMA. 72 | """ 73 | return _mf_select_test(scc, "a0", "0000", ["3f00", "7f25"]) 74 | 75 | 76 | class CardProfile: 77 | """A Card Profile describes a card, it's filesystem hierarchy, an [initial] list of 78 | applications as well as profile-specific SW and shell commands. Every card has 79 | one card profile, but there may be multiple applications within that profile.""" 80 | 81 | def __init__(self, name, **kw): 82 | """ 83 | Args: 84 | desc (str) : Description 85 | files_in_mf : List of CardEF instances present in MF 86 | applications : List of CardApplications present on card 87 | sw : List of status word definitions 88 | shell_cmdsets : List of cmd2 shell command sets of profile-specific commands 89 | cla : class byte that should be used with cards of this profile 90 | sel_ctrl : selection control bytes class byte that should be used with cards of this profile 91 | addons: List of optional CardAddons that a card of this profile might have 92 | """ 93 | self.name = name 94 | self.desc = kw.get("desc", None) 95 | self.files_in_mf = kw.get("files_in_mf", []) 96 | self.sw = kw.get("sw", {}) 97 | self.applications = kw.get("applications", []) 98 | self.shell_cmdsets = kw.get("shell_cmdsets", []) 99 | self.cla = kw.get("cla", "00") 100 | self.sel_ctrl = kw.get("sel_ctrl", "0004") 101 | # list of optional addons that a card of this profile might have 102 | self.addons = kw.get("addons", []) 103 | 104 | def __str__(self): 105 | return self.name 106 | 107 | def add_application(self, app: CardApplication): 108 | """Add an application to a card profile. 109 | 110 | Args: 111 | app : CardApplication instance to be added to profile 112 | """ 113 | self.applications.append(app) 114 | 115 | def interpret_sw(self, sw: str): 116 | """Interpret a given status word within the profile. 117 | 118 | Args: 119 | sw : Status word as string of 4 hex digits 120 | 121 | Returns: 122 | Tuple of two strings 123 | """ 124 | return interpret_sw(self.sw, sw) 125 | 126 | @staticmethod 127 | def decode_select_response(data_hex: str) -> object: 128 | """Decode the response to a SELECT command. 129 | 130 | This is the fall-back method which doesn't perform any decoding. It mostly 131 | exists so specific derived classes can overload it for actual decoding. 132 | This method is implemented in the profile and is only used when application 133 | specific decoding cannot be performed (no ADF is selected). 134 | 135 | Args: 136 | data_hex: Hex string of the select response 137 | """ 138 | return data_hex 139 | 140 | @staticmethod 141 | @abc.abstractmethod 142 | def match_with_card(scc: SimCardCommands) -> bool: 143 | """Check if the specific profile matches the card. This method is a 144 | placeholder that is overloaded by specific dirived classes. The method 145 | actively probes the card to make sure the profile class matches the 146 | physical card. This usually also means that the card is reset during 147 | the process, so this method must not be called at random times. It may 148 | only be called on startup. 149 | 150 | Args: 151 | scc: SimCardCommands class 152 | Returns: 153 | match = True, no match = False 154 | """ 155 | return False 156 | 157 | @staticmethod 158 | def pick(scc: SimCardCommands): 159 | profiles = list(all_subclasses(CardProfile)) 160 | profiles.sort(key=operator.attrgetter('ORDER')) 161 | 162 | for p in profiles: 163 | if p.match_with_card(scc): 164 | return p() 165 | 166 | return None 167 | 168 | def add_addon(self, addon: 'CardProfileAddon'): 169 | assert(addon not in self.addons) 170 | # we don't install any additional files, as that is happening in the RuntimeState. 171 | self.addons.append(addon) 172 | 173 | class CardProfileAddon(abc.ABC): 174 | """A Card Profile Add-on is something that is not a card application or a full stand-alone 175 | card profile, but an add-on to an existing profile. Think of GSM-R specific files existing 176 | on what is otherwise a SIM or USIM+SIM card.""" 177 | 178 | def __init__(self, name: str, **kw): 179 | """ 180 | Args: 181 | desc (str) : Description 182 | files_in_mf : List of CardEF instances present in MF 183 | shell_cmdsets : List of cmd2 shell command sets of profile-specific commands 184 | """ 185 | self.name = name 186 | self.desc = kw.get("desc", None) 187 | self.files_in_mf = kw.get("files_in_mf", []) 188 | self.shell_cmdsets = kw.get("shell_cmdsets", []) 189 | pass 190 | 191 | def __str__(self): 192 | return self.name 193 | 194 | @abc.abstractmethod 195 | def probe(self, card: 'CardBase') -> bool: 196 | """Probe a given card to determine whether or not this add-on is present/supported.""" 197 | pass 198 | -------------------------------------------------------------------------------- /pySim/transport/calypso.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2018 Vadim Yanitskiy 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | import select 20 | import struct 21 | import socket 22 | import os 23 | import argparse 24 | from typing import Optional 25 | 26 | from pySim.transport import LinkBase 27 | from pySim.exceptions import * 28 | from pySim.utils import h2b, b2h, Hexstr, ResTuple 29 | 30 | 31 | class L1CTLMessage: 32 | 33 | # Every (encoded) L1CTL message has the following structure: 34 | # - msg_length (2 bytes, net order) 35 | # - l1ctl_hdr (packed structure) 36 | # - msg_type 37 | # - flags 38 | # - padding (2 spare bytes) 39 | # - ... payload ... 40 | 41 | def __init__(self, msg_type, flags=0x00): 42 | # Init L1CTL message header 43 | self.data = struct.pack("BBxx", msg_type, flags) 44 | 45 | def gen_msg(self): 46 | return struct.pack("!H", len(self.data)) + self.data 47 | 48 | 49 | class L1CTLMessageReset(L1CTLMessage): 50 | 51 | # L1CTL message types 52 | L1CTL_RESET_REQ = 0x0d 53 | L1CTL_RESET_IND = 0x07 54 | L1CTL_RESET_CONF = 0x0e 55 | 56 | # Reset types 57 | L1CTL_RES_T_BOOT = 0x00 58 | L1CTL_RES_T_FULL = 0x01 59 | L1CTL_RES_T_SCHED = 0x02 60 | 61 | def __init__(self, type=L1CTL_RES_T_FULL): 62 | super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ) 63 | self.data += struct.pack("Bxxx", type) 64 | 65 | 66 | class L1CTLMessageSIM(L1CTLMessage): 67 | 68 | # SIM related message types 69 | L1CTL_SIM_REQ = 0x16 70 | L1CTL_SIM_CONF = 0x17 71 | 72 | def __init__(self, pdu): 73 | super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ) 74 | self.data += pdu 75 | 76 | 77 | class CalypsoSimLink(LinkBase): 78 | """Transport Link for Calypso based phones.""" 79 | 80 | def __init__(self, sock_path: str = "/tmp/osmocom_l2", **kwargs): 81 | super().__init__(**kwargs) 82 | if os.environ.get('PYSIM_INTEGRATION_TEST') == "1": 83 | print("Using Calypso-based (OsmocomBB) reader interface") 84 | else: 85 | print("Using Calypso-based (OsmocomBB) reader at socket %s" % sock_path) 86 | # Make sure that a given socket path exists 87 | if not os.path.exists(sock_path): 88 | raise ReaderError( 89 | "There is no such ('%s') UNIX socket" % sock_path) 90 | 91 | print("Connecting to osmocon at '%s'..." % sock_path) 92 | 93 | # Establish a client connection 94 | self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 95 | self.sock.connect(sock_path) 96 | 97 | # Remember socket path 98 | self._sock_path = sock_path 99 | 100 | def __del__(self): 101 | self.sock.close() 102 | 103 | def wait_for_rsp(self, exp_len: int = 128): 104 | # Wait for incoming data (timeout is 3 seconds) 105 | s, _, _ = select.select([self.sock], [], [], 3.0) 106 | if not s: 107 | raise ReaderError("Timeout waiting for card response") 108 | 109 | # Receive expected amount of bytes from osmocon 110 | rsp = self.sock.recv(exp_len) 111 | return rsp 112 | 113 | def reset_card(self): 114 | # Request FULL reset 115 | req_msg = L1CTLMessageReset() 116 | self.sock.send(req_msg.gen_msg()) 117 | 118 | # Wait for confirmation 119 | rsp = self.wait_for_rsp() 120 | rsp_msg = struct.unpack_from("!HB", rsp) 121 | if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF: 122 | raise ReaderError("Failed to reset Calypso PHY") 123 | 124 | def connect(self): 125 | self.reset_card() 126 | 127 | def disconnect(self): 128 | pass # Nothing to do really ... 129 | 130 | def wait_for_card(self, timeout: Optional[int] = None, newcardonly: bool = False): 131 | pass # Nothing to do really ... 132 | 133 | def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple: 134 | 135 | # Request FULL reset 136 | req_msg = L1CTLMessageSIM(h2b(pdu)) 137 | self.sock.send(req_msg.gen_msg()) 138 | 139 | # Read message length first 140 | rsp = self.wait_for_rsp(struct.calcsize("!H")) 141 | msg_len = struct.unpack_from("!H", rsp)[0] 142 | if msg_len < struct.calcsize("BBxx"): 143 | raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF") 144 | 145 | # Read the whole message then 146 | rsp = self.sock.recv(msg_len) 147 | 148 | # Verify L1CTL header 149 | hdr = struct.unpack_from("BBxx", rsp) 150 | if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF: 151 | raise ReaderError("Unexpected L1CTL message received") 152 | 153 | # Verify the payload length 154 | offset = struct.calcsize("BBxx") 155 | if len(rsp) <= offset: 156 | raise ProtocolError("Empty response from SIM?!?") 157 | 158 | # Omit L1CTL header 159 | rsp = rsp[offset:] 160 | 161 | # Unpack data and SW 162 | data = rsp[:-2] 163 | sw = rsp[-2:] 164 | 165 | return b2h(data), b2h(sw) 166 | 167 | def __str__(self) -> str: 168 | return "osmocon:%s" % (self._sock_path) 169 | 170 | @staticmethod 171 | def argparse_add_reader_args(arg_parser: argparse.ArgumentParser): 172 | osmobb_group = arg_parser.add_argument_group('OsmocomBB Reader') 173 | osmobb_group.add_argument('--osmocon', dest='osmocon_sock', metavar='PATH', default=None, 174 | help='Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)') 175 | -------------------------------------------------------------------------------- /pySim/transport/modem_atcmd.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2020 Vadim Yanitskiy 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | import logging as log 20 | import serial 21 | import time 22 | import re 23 | import argparse 24 | import os 25 | from typing import Optional 26 | 27 | from pySim.utils import Hexstr, ResTuple 28 | from pySim.transport import LinkBase 29 | from pySim.exceptions import * 30 | 31 | # HACK: if somebody needs to debug this thing 32 | # log.root.setLevel(log.DEBUG) 33 | 34 | 35 | class ModemATCommandLink(LinkBase): 36 | """Transport Link for 3GPP TS 27.007 compliant modems.""" 37 | 38 | def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 115200, **kwargs): 39 | super().__init__(**kwargs) 40 | if os.environ.get('PYSIM_INTEGRATION_TEST') == "1": 41 | print("Using modem for Generic SIM Access (3GPP TS 27.007)") 42 | else: 43 | print("Using modem for Generic SIM Access (3GPP TS 27.007) at port %s" % device) 44 | self._sl = serial.Serial(device, baudrate, timeout=5) 45 | self._echo = False # this will be auto-detected by _check_echo() 46 | self._device = device 47 | self._atr = None 48 | 49 | # Check the AT interface 50 | self._check_echo() 51 | 52 | # Trigger initial reset 53 | self.reset_card() 54 | 55 | def __del__(self): 56 | if hasattr(self, '_sl'): 57 | self._sl.close() 58 | 59 | def send_at_cmd(self, cmd, timeout=0.2, patience=0.002): 60 | # Convert from string to bytes, if needed 61 | bcmd = cmd if type(cmd) is bytes else cmd.encode() 62 | bcmd += b'\r' 63 | 64 | # Clean input buffer from previous/unexpected data 65 | self._sl.reset_input_buffer() 66 | 67 | # Send command to the modem 68 | log.debug('Sending AT command: %s', cmd) 69 | try: 70 | wlen = self._sl.write(bcmd) 71 | assert(wlen == len(bcmd)) 72 | except: 73 | raise ReaderError('Failed to send AT command: %s' % cmd) 74 | 75 | rsp = b'' 76 | its = 1 77 | t_start = time.time() 78 | while True: 79 | rsp = rsp + self._sl.read(self._sl.in_waiting) 80 | lines = rsp.split(b'\r\n') 81 | if len(lines) >= 2: 82 | res = lines[-2] 83 | if res == b'OK': 84 | log.debug('Command finished with result: %s', res) 85 | break 86 | if res == b'ERROR' or res.startswith(b'+CME ERROR:'): 87 | log.error('Command failed with result: %s', res) 88 | break 89 | 90 | if time.time() - t_start >= timeout: 91 | log.info('Command finished with timeout >= %ss', timeout) 92 | break 93 | time.sleep(patience) 94 | its += 1 95 | log.debug('Command took %0.6fs (%d cycles a %fs)', 96 | time.time() - t_start, its, patience) 97 | 98 | if self._echo: 99 | # Skip echo chars 100 | rsp = rsp[wlen:] 101 | rsp = rsp.strip() 102 | rsp = rsp.split(b'\r\n\r\n') 103 | 104 | log.debug('Got response from modem: %s', rsp) 105 | return rsp 106 | 107 | def _check_echo(self): 108 | """Verify the correct response to 'AT' command 109 | and detect if inputs are echoed by the device 110 | 111 | Although echo of inputs can be enabled/disabled via 112 | ATE1/ATE0, respectively, we rather detect the current 113 | configuration of the modem without any change. 114 | """ 115 | # Next command shall not strip the echo from the response 116 | self._echo = False 117 | result = self.send_at_cmd('AT') 118 | 119 | # Verify the response 120 | if len(result) > 0: 121 | if result[-1] == b'OK': 122 | self._echo = False 123 | return 124 | elif result[-1] == b'AT\r\r\nOK': 125 | self._echo = True 126 | return 127 | raise ReaderError( 128 | 'Interface \'%s\' does not respond to \'AT\' command' % self._device) 129 | 130 | def reset_card(self): 131 | # Reset the modem, just to be sure 132 | if self.send_at_cmd('ATZ') != [b'OK']: 133 | raise ReaderError('Failed to reset the modem') 134 | 135 | # Make sure that generic SIM access is supported 136 | if self.send_at_cmd('AT+CSIM=?') != [b'OK']: 137 | raise ReaderError('The modem does not seem to support SIM access') 138 | 139 | log.info('Modem at \'%s\' is ready!' % self._device) 140 | 141 | def connect(self): 142 | pass # Nothing to do really ... 143 | 144 | def disconnect(self): 145 | pass # Nothing to do really ... 146 | 147 | def wait_for_card(self, timeout: Optional[int] = None, newcardonly: bool = False): 148 | pass # Nothing to do really ... 149 | 150 | def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple: 151 | # Make sure pdu has upper case hex digits [A-F] 152 | pdu = pdu.upper() 153 | 154 | # Prepare the command as described in 8.17 155 | cmd = 'AT+CSIM=%d,\"%s\"' % (len(pdu), pdu) 156 | log.debug('Sending command: %s', cmd) 157 | 158 | # Send AT+CSIM command to the modem 159 | rsp = self.send_at_cmd(cmd) 160 | if rsp[-1].startswith(b'+CME ERROR:'): 161 | raise ProtocolError('AT+CSIM failed with: %s' % str(rsp)) 162 | if len(rsp) != 2 or rsp[-1] != b'OK': 163 | raise ReaderError('APDU transfer failed: %s' % str(rsp)) 164 | rsp = rsp[0] # Get rid of b'OK' 165 | 166 | # Make sure that the response has format: b'+CSIM: %d,\"%s\"' 167 | try: 168 | result = re.match(b'\+CSIM: (\d+),\"([0-9A-F]+)\"', rsp) 169 | (rsp_pdu_len, rsp_pdu) = result.groups() 170 | except: 171 | raise ReaderError('Failed to parse response from modem: %s' % rsp) 172 | 173 | # TODO: make sure we have at least SW 174 | data = rsp_pdu[:-4].decode().lower() 175 | sw = rsp_pdu[-4:].decode().lower() 176 | log.debug('Command response: %s, %s', data, sw) 177 | return data, sw 178 | 179 | def __str__(self) -> str: 180 | return "modem:%s" % self._device 181 | 182 | @staticmethod 183 | def argparse_add_reader_args(arg_parser: argparse.ArgumentParser): 184 | modem_group = arg_parser.add_argument_group('AT Command Modem Reader') 185 | modem_group.add_argument('--modem-device', dest='modem_dev', metavar='DEV', default=None, 186 | help='Serial port of modem for Generic SIM Access (3GPP TS 27.007)') 187 | modem_group.add_argument('--modem-baud', type=int, metavar='BAUD', default=115200, 188 | help='Baud rate used for modem port') 189 | -------------------------------------------------------------------------------- /pySim/transport/pcsc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2009-2010 Sylvain Munaut 4 | # Copyright (C) 2010 Harald Welte 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | # 19 | 20 | import argparse 21 | import os 22 | from typing import Optional 23 | 24 | from smartcard.CardConnection import CardConnection 25 | from smartcard.CardRequest import CardRequest 26 | from smartcard.Exceptions import NoCardException, CardRequestTimeoutException, CardConnectionException, CardConnectionException 27 | from smartcard.System import readers 28 | 29 | from pySim.exceptions import NoCardError, ProtocolError, ReaderError 30 | from pySim.transport import LinkBase 31 | from pySim.utils import h2i, i2h, Hexstr, ResTuple 32 | 33 | 34 | class PcscSimLink(LinkBase): 35 | """ pySim: PCSC reader transport link.""" 36 | 37 | def __init__(self, reader_number: int = 0, **kwargs): 38 | super().__init__(**kwargs) 39 | if os.environ.get('PYSIM_INTEGRATION_TEST') == "1": 40 | print("Using PC/SC reader interface") 41 | else: 42 | print("Using PC/SC reader number %u" % reader_number) 43 | r = readers() 44 | if reader_number >= len(r): 45 | raise ReaderError('No reader found for number %d' % reader_number) 46 | self._reader = r[reader_number] 47 | self._con = self._reader.createConnection() 48 | self._reader_number = reader_number 49 | 50 | def __del__(self): 51 | try: 52 | # FIXME: this causes multiple warnings in Python 3.5.3 53 | self._con.disconnect() 54 | except: 55 | pass 56 | return 57 | 58 | def wait_for_card(self, timeout: Optional[int] = None, newcardonly: bool = False): 59 | cr = CardRequest(readers=[self._reader], 60 | timeout=timeout, newcardonly=newcardonly) 61 | try: 62 | cr.waitforcard() 63 | except CardRequestTimeoutException: 64 | raise NoCardError() 65 | self.connect() 66 | 67 | def connect(self): 68 | try: 69 | # To avoid leakage of resources, make sure the reader 70 | # is disconnected 71 | self.disconnect() 72 | 73 | # Explicitly select T=0 communication protocol 74 | self._con.connect(CardConnection.T0_protocol) 75 | except CardConnectionException: 76 | raise ProtocolError() 77 | except NoCardException: 78 | raise NoCardError() 79 | 80 | def get_atr(self) -> Hexstr: 81 | return self._con.getATR() 82 | 83 | def disconnect(self): 84 | self._con.disconnect() 85 | 86 | def reset_card(self): 87 | self.disconnect() 88 | self.connect() 89 | return 1 90 | 91 | def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple: 92 | 93 | apdu = h2i(pdu) 94 | 95 | data, sw1, sw2 = self._con.transmit(apdu) 96 | 97 | sw = [sw1, sw2] 98 | 99 | # Return value 100 | return i2h(data), i2h(sw) 101 | 102 | def __str__(self) -> str: 103 | return "PCSC:%u[%s]" % (self._reader_number, self._reader) 104 | 105 | @staticmethod 106 | def argparse_add_reader_args(arg_parser: argparse.ArgumentParser): 107 | pcsc_group = arg_parser.add_argument_group('PC/SC Reader') 108 | pcsc_group.add_argument('-p', '--pcsc-device', type=int, dest='pcsc_dev', metavar='PCSC', default=None, 109 | help='PC/SC reader number to use for SIM access') 110 | -------------------------------------------------------------------------------- /pySim/transport/serial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Copyright (C) 2009-2010 Sylvain Munaut 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | # 18 | 19 | import serial 20 | import time 21 | import os 22 | import argparse 23 | from typing import Optional 24 | 25 | from pySim.exceptions import NoCardError, ProtocolError 26 | from pySim.transport import LinkBase 27 | from pySim.utils import h2b, b2h, Hexstr, ResTuple 28 | 29 | 30 | class SerialSimLink(LinkBase): 31 | """ pySim: Transport Link for serial (RS232) based readers included with simcard""" 32 | 33 | def __init__(self, device: str = '/dev/ttyUSB0', baudrate: int = 9600, rst: str = '-rts', 34 | debug: bool = False, **kwargs): 35 | super().__init__(**kwargs) 36 | if os.environ.get('PYSIM_INTEGRATION_TEST') == "1": 37 | print("Using serial reader interface") 38 | else: 39 | print("Using serial reader interface at port %s" % device) 40 | if not os.path.exists(device): 41 | raise ValueError("device file %s does not exist -- abort" % device) 42 | self._sl = serial.Serial( 43 | port=device, 44 | parity=serial.PARITY_EVEN, 45 | bytesize=serial.EIGHTBITS, 46 | stopbits=serial.STOPBITS_TWO, 47 | timeout=1, 48 | xonxoff=0, 49 | rtscts=0, 50 | baudrate=baudrate, 51 | ) 52 | self._rst_pin = rst 53 | self._debug = debug 54 | self._atr = None 55 | 56 | def __del__(self): 57 | if (hasattr(self, "_sl")): 58 | self._sl.close() 59 | 60 | def wait_for_card(self, timeout: Optional[int] = None, newcardonly: bool = False): 61 | # Direct try 62 | existing = False 63 | 64 | try: 65 | self.reset_card() 66 | if not newcardonly: 67 | return 68 | else: 69 | existing = True 70 | except NoCardError: 71 | pass 72 | 73 | # Poll ... 74 | mt = time.time() + timeout if timeout is not None else None 75 | pe = 0 76 | 77 | while (mt is None) or (time.time() < mt): 78 | try: 79 | time.sleep(0.5) 80 | self.reset_card() 81 | if not existing: 82 | return 83 | except NoCardError: 84 | existing = False 85 | except ProtocolError: 86 | if existing: 87 | existing = False 88 | else: 89 | # Tolerate a couple of protocol error ... can happen if 90 | # we try when the card is 'half' inserted 91 | pe += 1 92 | if (pe > 2): 93 | raise 94 | 95 | # Timed out ... 96 | raise NoCardError() 97 | 98 | def connect(self): 99 | self.reset_card() 100 | 101 | def get_atr(self) -> Hexstr: 102 | return self._atr 103 | 104 | def disconnect(self): 105 | pass # Nothing to do really ... 106 | 107 | def reset_card(self): 108 | rv = self._reset_card() 109 | if rv == 0: 110 | raise NoCardError() 111 | elif rv < 0: 112 | raise ProtocolError() 113 | return rv 114 | 115 | def _reset_card(self): 116 | self._atr = None 117 | rst_meth_map = { 118 | 'rts': self._sl.setRTS, 119 | 'dtr': self._sl.setDTR, 120 | } 121 | rst_val_map = {'+': 0, '-': 1} 122 | 123 | try: 124 | rst_meth = rst_meth_map[self._rst_pin[1:]] 125 | rst_val = rst_val_map[self._rst_pin[0]] 126 | except: 127 | raise ValueError('Invalid reset pin %s' % self._rst_pin) 128 | 129 | rst_meth(rst_val) 130 | time.sleep(0.1) # 100 ms 131 | self._sl.flushInput() 132 | rst_meth(rst_val ^ 1) 133 | 134 | b = self._rx_byte() 135 | if not b: 136 | return 0 137 | if ord(b) != 0x3b: 138 | return -1 139 | self._dbg_print("TS: 0x%x Direct convention" % ord(b)) 140 | 141 | while ord(b) == 0x3b: 142 | b = self._rx_byte() 143 | 144 | if not b: 145 | return -1 146 | t0 = ord(b) 147 | self._dbg_print("T0: 0x%x" % t0) 148 | self._atr = [0x3b, ord(b)] 149 | 150 | for i in range(4): 151 | if t0 & (0x10 << i): 152 | b = self._rx_byte() 153 | self._atr.append(ord(b)) 154 | self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b))) 155 | 156 | for i in range(0, t0 & 0xf): 157 | b = self._rx_byte() 158 | self._atr.append(ord(b)) 159 | self._dbg_print("Historical = %x" % ord(b)) 160 | 161 | while True: 162 | x = self._rx_byte() 163 | if not x: 164 | break 165 | self._atr.append(ord(x)) 166 | self._dbg_print("Extra: %x" % ord(x)) 167 | 168 | return 1 169 | 170 | def _dbg_print(self, s): 171 | if self._debug: 172 | print(s) 173 | 174 | def _tx_byte(self, b): 175 | self._sl.write(b) 176 | r = self._sl.read() 177 | if r != b: # TX and RX are tied, so we must clear the echo 178 | raise ProtocolError("Bad echo value. Expected %02x, got %s)" % ( 179 | ord(b), '%02x' % ord(r) if r else '(nil)')) 180 | 181 | def _tx_string(self, s): 182 | """This is only safe if it's guaranteed the card won't send any data 183 | during the time of tx of the string !!!""" 184 | self._sl.write(s) 185 | r = self._sl.read(len(s)) 186 | if r != s: # TX and RX are tied, so we must clear the echo 187 | raise ProtocolError( 188 | "Bad echo value (Expected: %s, got %s)" % (b2h(s), b2h(r))) 189 | 190 | def _rx_byte(self): 191 | return self._sl.read() 192 | 193 | def _send_apdu_raw(self, pdu: Hexstr) -> ResTuple: 194 | 195 | pdu = h2b(pdu) 196 | data_len = pdu[4] # P3 197 | 198 | # Send first CLASS,INS,P1,P2,P3 199 | self._tx_string(pdu[0:5]) 200 | 201 | # Wait ack which can be 202 | # - INS: Command acked -> go ahead 203 | # - 0x60: NULL, just wait some more 204 | # - SW1: The card can apparently proceed ... 205 | while True: 206 | b = self._rx_byte() 207 | if ord(b) == pdu[1]: 208 | break 209 | elif b != '\x60': 210 | # Ok, it 'could' be SW1 211 | sw1 = b 212 | sw2 = self._rx_byte() 213 | nil = self._rx_byte() 214 | if (sw2 and not nil): 215 | return '', b2h(sw1+sw2) 216 | 217 | raise ProtocolError() 218 | 219 | # Send data (if any) 220 | if len(pdu) > 5: 221 | self._tx_string(pdu[5:]) 222 | 223 | # Receive data (including SW !) 224 | # length = [P3 - tx_data (=len(pdu)-len(hdr)) + 2 (SW1//2) ] 225 | to_recv = data_len - len(pdu) + 5 + 2 226 | 227 | data = bytes(0) 228 | while (len(data) < to_recv): 229 | b = self._rx_byte() 230 | if (to_recv == 2) and (b == '\x60'): # Ignore NIL if we have no RX data (hack ?) 231 | continue 232 | if not b: 233 | break 234 | data += b 235 | 236 | # Split datafield from SW 237 | if len(data) < 2: 238 | return None, None 239 | sw = data[-2:] 240 | data = data[0:-2] 241 | 242 | # Return value 243 | return b2h(data), b2h(sw) 244 | 245 | def __str__(self) -> str: 246 | return "serial:%s" % (self._sl.name) 247 | 248 | @staticmethod 249 | def argparse_add_reader_args(arg_parser: argparse.ArgumentParser): 250 | serial_group = arg_parser.add_argument_group('Serial Reader') 251 | serial_group.add_argument('-d', '--device', metavar='DEV', default='/dev/ttyUSB0', 252 | help='Serial Device for SIM access') 253 | serial_group.add_argument('-b', '--baud', dest='baudrate', type=int, metavar='BAUD', default=9600, 254 | help='Baud rate used for SIM access') 255 | -------------------------------------------------------------------------------- /pySim/ts_31_104.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | Support for 3GPP TS 31.104 V17.0.0 5 | """ 6 | 7 | # Copyright (C) 2023 Harald Welte 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation, either version 2 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program. If not, see . 21 | # 22 | 23 | from pySim.filesystem import * 24 | from pySim.utils import * 25 | from pySim.tlv import * 26 | from pySim.ts_31_102 import ADF_USIM 27 | from pySim.ts_51_011 import EF_IMSI, EF_AD 28 | import pySim.ts_102_221 29 | from pySim.ts_102_221 import EF_ARR 30 | 31 | 32 | class ADF_HPSIM(CardADF): 33 | def __init__(self, aid='a000000087100A', has_fs=True, name='ADF.HPSIM', fid=None, sfid=None, 34 | desc='HPSIM Application'): 35 | super().__init__(aid=aid, has_fs=has_fs, fid=fid, sfid=sfid, name=name, desc=desc) 36 | 37 | files = [ 38 | EF_ARR(fid='6f06', sfid=0x06), 39 | EF_IMSI(fid='6f07', sfid=0x07), 40 | EF_AD(fid='6fad', sfid=0x03), 41 | ] 42 | self.add_files(files) 43 | # add those commands to the general commands of a TransparentEF 44 | self.shell_commands += [ADF_USIM.AddlShellCommands()] 45 | 46 | def decode_select_response(self, data_hex): 47 | return pySim.ts_102_221.CardProfileUICC.decode_select_response(data_hex) 48 | 49 | 50 | # TS 31.104 Section 7.1 51 | sw_hpsim = { 52 | 'Security management': { 53 | '9862': 'Authentication error, incorrect MAC', 54 | } 55 | } 56 | 57 | 58 | class CardApplicationHPSIM(CardApplication): 59 | def __init__(self): 60 | super().__init__('HPSIM', adf=ADF_HPSIM(), sw=sw_hpsim) 61 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /pysim-testdata/Fairwaves-SIM.data: -------------------------------------------------------------------------------- 1 | MCC=001 2 | MNC=01 3 | IMSI=001010000000111 4 | ADM_HEX=CAE743DB9C5B5A58 5 | 6 | -------------------------------------------------------------------------------- /pysim-testdata/Fairwaves-SIM.ok: -------------------------------------------------------------------------------- 1 | Using PC/SC reader interface 2 | Reading ... 3 | Autodetected card type: Fairwaves-SIM 4 | ICCID: 8988219000000117833 5 | IMSI: 001010000000111 6 | GID1: ffffffffffffffff 7 | GID2: ffffffffffffffff 8 | SMSP: e1ffffffffffffffffffffffff0581005155f5ffffffffffff000000ffffffffffffffffffffffffffff 9 | SPN: Fairwaves 10 | Show in HPLMN: False 11 | Hide in OPLMN: False 12 | PLMNsel: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 13 | PLMNwAcT: 14 | ffffff0000 # unused 15 | ffffff0000 # unused 16 | ffffff0000 # unused 17 | ffffff0000 # unused 18 | ffffff0000 # unused 19 | ffffff0000 # unused 20 | ffffff0000 # unused 21 | ffffff0000 # unused 22 | 23 | OPLMNwAcT: 24 | ffffff0000 # unused 25 | ffffff0000 # unused 26 | ffffff0000 # unused 27 | ffffff0000 # unused 28 | ffffff0000 # unused 29 | ffffff0000 # unused 30 | ffffff0000 # unused 31 | ffffff0000 # unused 32 | 33 | HPLMNAcT: 34 | 00f110ffff # MCC: 001 MNC: 01 AcT: UTRAN, E-UTRAN WB-S1, E-UTRAN NB-S1, NG-RAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT 35 | ffffff0000 # unused 36 | ffffff0000 # unused 37 | ffffff0000 # unused 38 | ffffff0000 # unused 39 | ffffff0000 # unused 40 | ffffff0000 # unused 41 | ffffff0000 # unused 42 | 43 | ACC: 0008 44 | MSISDN: Not available 45 | Administrative data: 00000002 46 | MS operation mode: normal 47 | Ciphering Indicator: disabled 48 | SIM Service Table: ff3cc3ff030fff0f000fff03f0c0 49 | Service 1 - CHV1 disable function 50 | Service 2 - Abbreviated Dialling Numbers (ADN) 51 | Service 3 - Fixed Dialling Numbers (FDN) 52 | Service 4 - Short Message Storage (SMS) 53 | Service 5 - Advice of Charge (AoC) 54 | Service 6 - Capability Configuration Parameters (CCP) 55 | Service 7 - PLMN selector 56 | Service 8 - RFU 57 | Service 11 - Extension2 58 | Service 12 - SMS Parameters 59 | Service 13 - Last Number Dialled (LND) 60 | Service 14 - Cell Broadcast Message Identifier 61 | Service 17 - Service Provider Name 62 | Service 18 - Service Dialling Numbers (SDN) 63 | Service 23 - enhanced Multi-Level Precedence and Pre-emption Service 64 | Service 24 - Automatic Answer for eMLPP 65 | Service 25 - Data download via SMS-CB 66 | Service 26 - Data download via SMS-PP 67 | Service 27 - Menu selection 68 | Service 28 - Call control 69 | Service 29 - Proactive SIM 70 | Service 30 - Cell Broadcast Message Identifier Ranges 71 | Service 31 - Barred Dialling Numbers (BDN) 72 | Service 32 - Extension4 73 | Service 33 - De-personalization Control Keys 74 | Service 34 - Co-operative Network List 75 | Service 41 - USSD string data object supported in Call Control 76 | Service 42 - RUN AT COMMAND command 77 | Service 43 - User controlled PLMN Selector with Access Technology 78 | Service 44 - Operator controlled PLMN Selector with Access Technology 79 | Service 49 - MExE 80 | Service 50 - Reserved and shall be ignored 81 | Service 51 - PLMN Network Name 82 | Service 52 - Operator PLMN List 83 | Service 53 - Mailbox Dialling Numbers 84 | Service 54 - Message Waiting Indication Status 85 | Service 55 - Call Forwarding Indication Status 86 | Service 56 - Service Provider Display Information 87 | Service 57 - Multimedia Messaging Service (MMS) 88 | Service 58 - Extension 8 89 | Service 59 - MMS User Connectivity Parameters 90 | 91 | FPLMN: 92 | ffffff # unused 93 | ffffff # unused 94 | ffffff # unused 95 | ffffff # unused 96 | ffffff # unused 97 | ffffff # unused 98 | ffffff # unused 99 | ffffff # unused 100 | ffffff # unused 101 | ffffff # unused 102 | 103 | USIM Service Table: 01ea1ffc21360480010000 104 | Service 1 - Local Phone Book 105 | Service 10 - Short Message Storage (SMS) 106 | Service 12 - Short Message Service Parameters (SMSP) 107 | Service 14 - Capability Configuration Parameters 2 (CCP2) 108 | Service 15 - Cell Broadcast Message Identifier 109 | Service 16 - Cell Broadcast Message Identifier Ranges 110 | Service 17 - Group Identifier Level 1 111 | Service 18 - Group Identifier Level 2 112 | Service 19 - Service Provider Name 113 | Service 20 - User controlled PLMN selector with Access Technology 114 | Service 21 - MSISDN 115 | Service 27 - GSM Access 116 | Service 28 - Data download via SMS-PP 117 | Service 29 - Data download via SMS-CB 118 | Service 30 - Call Control by USIM 119 | Service 31 - MO-SMS Control by USIM 120 | Service 32 - RUN AT COMMAND command 121 | Service 33 - shall be set to 1 122 | Service 38 - GSM security context 123 | Service 42 - Operator controlled PLMN selector with Access Technology 124 | Service 43 - HPLMN selector with Access Technology 125 | Service 45 - PLMN Network Name 126 | Service 46 - Operator PLMN List 127 | Service 51 - Service Provider Display Information 128 | Service 64 - VGCS security 129 | Service 65 - VBS security 130 | 131 | Done ! 132 | 133 | -------------------------------------------------------------------------------- /pysim-testdata/Wavemobile-SIM.data: -------------------------------------------------------------------------------- 1 | MCC=001 2 | MNC=01 3 | IMSI=001010000000102 4 | ADM_HEX=15E31383624FDC8A 5 | 6 | -------------------------------------------------------------------------------- /pysim-testdata/Wavemobile-SIM.ok: -------------------------------------------------------------------------------- 1 | Using PC/SC reader interface 2 | Reading ... 3 | Autodetected card type: Wavemobile-SIM 4 | ICCID: 89445310150011013678 5 | IMSI: 001010000000102 6 | GID1: Can't read file -- SW match failed! Expected 9000 and got 6a82. 7 | GID2: Can't read file -- SW match failed! Expected 9000 and got 6a82. 8 | SMSP: e1ffffffffffffffffffffffff0581005155f5ffffffffffff000000ffffffffffffffffffffffffffff 9 | SPN: wavemobile 10 | Show in HPLMN: False 11 | Hide in OPLMN: False 12 | PLMNsel: 00f110ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 13 | PLMNwAcT: 14 | 00f110ffff # MCC: 001 MNC: 01 AcT: UTRAN, E-UTRAN WB-S1, E-UTRAN NB-S1, NG-RAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT 15 | ffffff0000 # unused 16 | ffffff0000 # unused 17 | ffffff0000 # unused 18 | ffffff0000 # unused 19 | ffffff0000 # unused 20 | ffffff0000 # unused 21 | ffffff0000 # unused 22 | ffffff0000 # unused 23 | ffffff0000 # unused 24 | ffffff0000 # unused 25 | ffffff0000 # unused 26 | ffffff0000 # unused 27 | ffffff0000 # unused 28 | ffffff0000 # unused 29 | ffffff0000 # unused 30 | 31 | OPLMNwAcT: 32 | 00f110ffff # MCC: 001 MNC: 01 AcT: UTRAN, E-UTRAN WB-S1, E-UTRAN NB-S1, NG-RAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT 33 | ffffff0000 # unused 34 | ffffff0000 # unused 35 | ffffff0000 # unused 36 | ffffff0000 # unused 37 | ffffff0000 # unused 38 | ffffff0000 # unused 39 | ffffff0000 # unused 40 | ffffff0000 # unused 41 | ffffff0000 # unused 42 | ffffff0000 # unused 43 | ffffff0000 # unused 44 | ffffff0000 # unused 45 | ffffff0000 # unused 46 | ffffff0000 # unused 47 | ffffff0000 # unused 48 | 49 | HPLMNAcT: Can't read file -- SW match failed! Expected 9000 and got 6a82. 50 | ACC: abce 51 | MSISDN: Not available 52 | Administrative data: 00000102 53 | MS operation mode: normal 54 | Ciphering Indicator: enabled 55 | SIM Service Table: ff33ff0f3c00ff0f000cf0c0f0030000 56 | Service 1 - CHV1 disable function 57 | Service 2 - Abbreviated Dialling Numbers (ADN) 58 | Service 3 - Fixed Dialling Numbers (FDN) 59 | Service 4 - Short Message Storage (SMS) 60 | Service 5 - Advice of Charge (AoC) 61 | Service 6 - Capability Configuration Parameters (CCP) 62 | Service 7 - PLMN selector 63 | Service 8 - RFU 64 | Service 9 - MSISDN 65 | Service 10 - Extension1 66 | Service 13 - Last Number Dialled (LND) 67 | Service 14 - Cell Broadcast Message Identifier 68 | Service 17 - Service Provider Name 69 | Service 18 - Service Dialling Numbers (SDN) 70 | Service 19 - Extension3 71 | Service 20 - RFU 72 | Service 21 - VGCS Group Identifier List (EFVGCS and EFVGCSS) 73 | Service 22 - VBS Group Identifier List (EFVBS and EFVBSS) 74 | Service 23 - enhanced Multi-Level Precedence and Pre-emption Service 75 | Service 24 - Automatic Answer for eMLPP 76 | Service 25 - Data download via SMS-CB 77 | Service 26 - Data download via SMS-PP 78 | Service 27 - Menu selection 79 | Service 28 - Call control 80 | Service 35 - Short Message Status Reports 81 | Service 36 - Network's indication of alerting in the MS 82 | Service 37 - Mobile Originated Short Message control by SIM 83 | Service 38 - GPRS 84 | Service 49 - MExE 85 | Service 50 - Reserved and shall be ignored 86 | Service 51 - PLMN Network Name 87 | Service 52 - Operator PLMN List 88 | Service 53 - Mailbox Dialling Numbers 89 | Service 54 - Message Waiting Indication Status 90 | Service 55 - Call Forwarding Indication Status 91 | Service 56 - Service Provider Display Information 92 | Service 57 - Multimedia Messaging Service (MMS) 93 | Service 58 - Extension 8 94 | Service 59 - MMS User Connectivity Parameters 95 | 96 | FPLMN: 97 | ffffff # unused 98 | ffffff # unused 99 | ffffff # unused 100 | ffffff # unused 101 | 102 | USIM Service Table: 9eff1b3c37fe5900000000 103 | Service 2 - Fixed Dialling Numbers (FDN) 104 | Service 3 - Extension 2 105 | Service 4 - Service Dialling Numbers (SDN) 106 | Service 5 - Extension3 107 | Service 8 - Outgoing Call Information (OCI and OCT) 108 | Service 9 - Incoming Call Information (ICI and ICT) 109 | Service 10 - Short Message Storage (SMS) 110 | Service 11 - Short Message Status Reports (SMSR) 111 | Service 12 - Short Message Service Parameters (SMSP) 112 | Service 13 - Advice of Charge (AoC) 113 | Service 14 - Capability Configuration Parameters 2 (CCP2) 114 | Service 15 - Cell Broadcast Message Identifier 115 | Service 16 - Cell Broadcast Message Identifier Ranges 116 | Service 17 - Group Identifier Level 1 117 | Service 18 - Group Identifier Level 2 118 | Service 20 - User controlled PLMN selector with Access Technology 119 | Service 21 - MSISDN 120 | Service 27 - GSM Access 121 | Service 28 - Data download via SMS-PP 122 | Service 29 - Data download via SMS-CB 123 | Service 30 - Call Control by USIM 124 | Service 33 - shall be set to 1 125 | Service 34 - Enabled Services Table 126 | Service 35 - APN Control List (ACL) 127 | Service 37 - Co-operative Network List 128 | Service 38 - GSM security context 129 | Service 42 - Operator controlled PLMN selector with Access Technology 130 | Service 43 - HPLMN selector with Access Technology 131 | Service 44 - Extension 5 132 | Service 45 - PLMN Network Name 133 | Service 46 - Operator PLMN List 134 | Service 47 - Mailbox Dialling Numbers 135 | Service 48 - Message Waiting Indication Status 136 | Service 49 - Call Forwarding Indication Status 137 | Service 52 - Multimedia Messaging Service (MMS) 138 | Service 53 - Extension 8 139 | Service 55 - MMS User Connectivity Parameters 140 | 141 | Done ! 142 | 143 | -------------------------------------------------------------------------------- /pysim-testdata/fakemagicsim.data: -------------------------------------------------------------------------------- 1 | MCC=001 2 | MNC=01 3 | ICCID=1122334455667788990 4 | KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 5 | OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 6 | IMSI=001010000000102 -------------------------------------------------------------------------------- /pysim-testdata/fakemagicsim.ok: -------------------------------------------------------------------------------- 1 | Using PC/SC reader interface 2 | Reading ... 3 | Autodetected card type: fakemagicsim 4 | ICCID: 1122334455667788990 5 | IMSI: 001010000000102 6 | GID1: Can't read file -- SW match failed! Expected 9000 and got 9404. 7 | GID2: Can't read file -- SW match failed! Expected 9000 and got 9404. 8 | SMSP: ffffffffffffffffffffffffe1ffffffffffffffffffffffff0581005155f5ffffffffffff000000 9 | SPN: Magic 10 | Show in HPLMN: True 11 | Hide in OPLMN: False 12 | PLMNsel: 00f110ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 13 | PLMNwAcT: Can't read file -- SW match failed! Expected 9000 and got 9404. 14 | OPLMNwAcT: Can't read file -- SW match failed! Expected 9000 and got 9404. 15 | HPLMNAcT: Can't read file -- SW match failed! Expected 9000 and got 9404. 16 | ACC: ffff 17 | MSISDN: Not available 18 | Administrative data: 000000 19 | MS operation mode: normal 20 | Ciphering Indicator: disabled 21 | SIM Service Table: ff3fff0f0300f003000c 22 | Service 1 - CHV1 disable function 23 | Service 2 - Abbreviated Dialling Numbers (ADN) 24 | Service 3 - Fixed Dialling Numbers (FDN) 25 | Service 4 - Short Message Storage (SMS) 26 | Service 5 - Advice of Charge (AoC) 27 | Service 6 - Capability Configuration Parameters (CCP) 28 | Service 7 - PLMN selector 29 | Service 8 - RFU 30 | Service 9 - MSISDN 31 | Service 10 - Extension1 32 | Service 11 - Extension2 33 | Service 12 - SMS Parameters 34 | Service 13 - Last Number Dialled (LND) 35 | Service 14 - Cell Broadcast Message Identifier 36 | Service 17 - Service Provider Name 37 | Service 18 - Service Dialling Numbers (SDN) 38 | Service 19 - Extension3 39 | Service 20 - RFU 40 | Service 21 - VGCS Group Identifier List (EFVGCS and EFVGCSS) 41 | Service 22 - VBS Group Identifier List (EFVBS and EFVBSS) 42 | Service 23 - enhanced Multi-Level Precedence and Pre-emption Service 43 | Service 24 - Automatic Answer for eMLPP 44 | Service 25 - Data download via SMS-CB 45 | Service 26 - Data download via SMS-PP 46 | Service 27 - Menu selection 47 | Service 28 - Call control 48 | Service 33 - De-personalization Control Keys 49 | Service 34 - Co-operative Network List 50 | Service 53 - Mailbox Dialling Numbers 51 | Service 54 - Message Waiting Indication Status 52 | Service 55 - Call Forwarding Indication Status 53 | Service 56 - Service Provider Display Information 54 | Service 57 - Multimedia Messaging Service (MMS) 55 | Service 58 - Extension 8 56 | 57 | Done ! 58 | 59 | -------------------------------------------------------------------------------- /pysim-testdata/pySim-trace_test_gsmtap.pcapng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simula/pysim/174fd32f17b7147724933cc3d8e33ebbb9541831/pysim-testdata/pySim-trace_test_gsmtap.pcapng -------------------------------------------------------------------------------- /pysim-testdata/sysmoISIM-SJA2.data: -------------------------------------------------------------------------------- 1 | MCC=001 2 | MNC=01 3 | ICCID=1122334455667788990 4 | KI=AABBCCDDEEFFAABBCCDDEEFFAABBCCDD 5 | OPC=12345678901234567890123456789012 6 | IMSI=001010000000102 7 | ADM=67225880 8 | -------------------------------------------------------------------------------- /pysim-testdata/sysmoISIM-SJA2.ok: -------------------------------------------------------------------------------- 1 | Using PC/SC reader interface 2 | Reading ... 3 | Autodetected card type: sysmoISIM-SJA2 4 | ICCID: 8988211000000467343 5 | IMSI: 001010000000102 6 | GID1: ffffffffffffffffffff 7 | GID2: ffffffffffffffffffff 8 | SMSP: ffffffffffffffffffffffffffffffffffffffffffffffffe1ffffffffffffffffffffffff0581005155f5ffffffffffff000000 9 | SPN: Magic 10 | Show in HPLMN: True 11 | Hide in OPLMN: True 12 | PLMNsel: 00f110ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 13 | PLMNwAcT: 14 | 00f110ffff # MCC: 001 MNC: 01 AcT: UTRAN, E-UTRAN WB-S1, E-UTRAN NB-S1, NG-RAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT 15 | ffffff0000 # unused 16 | ffffff0000 # unused 17 | ffffff0000 # unused 18 | ffffff0000 # unused 19 | ffffff0000 # unused 20 | ffffff0000 # unused 21 | ffffff0000 # unused 22 | ffffff0000 # unused 23 | ffffff0000 # unused 24 | ffffff0000 # unused 25 | ffffff0000 # unused 26 | 27 | OPLMNwAcT: 28 | 00f110ffff # MCC: 001 MNC: 01 AcT: UTRAN, E-UTRAN WB-S1, E-UTRAN NB-S1, NG-RAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT 29 | ffffff0000 # unused 30 | ffffff0000 # unused 31 | ffffff0000 # unused 32 | ffffff0000 # unused 33 | ffffff0000 # unused 34 | ffffff0000 # unused 35 | ffffff0000 # unused 36 | ffffff0000 # unused 37 | ffffff0000 # unused 38 | ffffff0000 # unused 39 | ffffff0000 # unused 40 | 41 | HPLMNAcT: 42 | 00f110ffff # MCC: 001 MNC: 01 AcT: UTRAN, E-UTRAN WB-S1, E-UTRAN NB-S1, NG-RAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT 43 | ffffff0000 # unused 44 | ffffff0000 # unused 45 | ffffff0000 # unused 46 | ffffff0000 # unused 47 | ffffff0000 # unused 48 | ffffff0000 # unused 49 | ffffff0000 # unused 50 | ffffff0000 # unused 51 | ffffff0000 # unused 52 | ffffff0000 # unused 53 | ffffff0000 # unused 54 | 55 | ACC: 0010 56 | MSISDN (NPI=1 ToN=3): 6766266 57 | Administrative data: 00000002 58 | MS operation mode: normal 59 | Ciphering Indicator: disabled 60 | SIM Service Table: ff33ffff3f003f0f300cf0c3f00000 61 | Service 1 - CHV1 disable function 62 | Service 2 - Abbreviated Dialling Numbers (ADN) 63 | Service 3 - Fixed Dialling Numbers (FDN) 64 | Service 4 - Short Message Storage (SMS) 65 | Service 5 - Advice of Charge (AoC) 66 | Service 6 - Capability Configuration Parameters (CCP) 67 | Service 7 - PLMN selector 68 | Service 8 - RFU 69 | Service 9 - MSISDN 70 | Service 10 - Extension1 71 | Service 13 - Last Number Dialled (LND) 72 | Service 14 - Cell Broadcast Message Identifier 73 | Service 17 - Service Provider Name 74 | Service 18 - Service Dialling Numbers (SDN) 75 | Service 19 - Extension3 76 | Service 20 - RFU 77 | Service 21 - VGCS Group Identifier List (EFVGCS and EFVGCSS) 78 | Service 22 - VBS Group Identifier List (EFVBS and EFVBSS) 79 | Service 23 - enhanced Multi-Level Precedence and Pre-emption Service 80 | Service 24 - Automatic Answer for eMLPP 81 | Service 25 - Data download via SMS-CB 82 | Service 26 - Data download via SMS-PP 83 | Service 27 - Menu selection 84 | Service 28 - Call control 85 | Service 29 - Proactive SIM 86 | Service 30 - Cell Broadcast Message Identifier Ranges 87 | Service 31 - Barred Dialling Numbers (BDN) 88 | Service 32 - Extension4 89 | Service 33 - De-personalization Control Keys 90 | Service 34 - Co-operative Network List 91 | Service 35 - Short Message Status Reports 92 | Service 36 - Network's indication of alerting in the MS 93 | Service 37 - Mobile Originated Short Message control by SIM 94 | Service 38 - GPRS 95 | Service 49 - MExE 96 | Service 50 - Reserved and shall be ignored 97 | Service 51 - PLMN Network Name 98 | Service 52 - Operator PLMN List 99 | Service 53 - Mailbox Dialling Numbers 100 | Service 54 - Message Waiting Indication Status 101 | Service 57 - Multimedia Messaging Service (MMS) 102 | Service 58 - Extension 8 103 | Service 59 - MMS User Connectivity Parameters 104 | 105 | EHPLMN: 106 | 00f110 # MCC: 001 MNC: 01 107 | ffffff # unused 108 | ffffff # unused 109 | ffffff # unused 110 | 111 | FPLMN: 112 | ffffff # unused 113 | ffffff # unused 114 | ffffff # unused 115 | ffffff # unused 116 | 117 | USIM Service Table: beff9f9de73e0408400170330000002e00000000 118 | Service 2 - Fixed Dialling Numbers (FDN) 119 | Service 3 - Extension 2 120 | Service 4 - Service Dialling Numbers (SDN) 121 | Service 5 - Extension3 122 | Service 6 - Barred Dialling Numbers (BDN) 123 | Service 8 - Outgoing Call Information (OCI and OCT) 124 | Service 9 - Incoming Call Information (ICI and ICT) 125 | Service 10 - Short Message Storage (SMS) 126 | Service 11 - Short Message Status Reports (SMSR) 127 | Service 12 - Short Message Service Parameters (SMSP) 128 | Service 13 - Advice of Charge (AoC) 129 | Service 14 - Capability Configuration Parameters 2 (CCP2) 130 | Service 15 - Cell Broadcast Message Identifier 131 | Service 16 - Cell Broadcast Message Identifier Ranges 132 | Service 17 - Group Identifier Level 1 133 | Service 18 - Group Identifier Level 2 134 | Service 19 - Service Provider Name 135 | Service 20 - User controlled PLMN selector with Access Technology 136 | Service 21 - MSISDN 137 | Service 24 - Enhanced Multi-Level Precedence and Pre-emption Service 138 | Service 25 - Automatic Answer for eMLPP 139 | Service 27 - GSM Access 140 | Service 28 - Data download via SMS-PP 141 | Service 29 - Data download via SMS-CB 142 | Service 32 - RUN AT COMMAND command 143 | Service 33 - shall be set to 1 144 | Service 34 - Enabled Services Table 145 | Service 35 - APN Control List (ACL) 146 | Service 38 - GSM security context 147 | Service 39 - CPBCCH Information 148 | Service 40 - Investigation Scan 149 | Service 42 - Operator controlled PLMN selector with Access Technology 150 | Service 43 - HPLMN selector with Access Technology 151 | Service 44 - Extension 5 152 | Service 45 - PLMN Network Name 153 | Service 46 - Operator PLMN List 154 | Service 51 - Service Provider Display Information 155 | Service 60 - User Controlled PLMN selector for I-WLAN access 156 | Service 71 - Equivalent HPLMN 157 | Service 73 - Equivalent HPLMN Presentation Indication 158 | Service 85 - EPS Mobility Management Information 159 | Service 86 - Allowed CSG Lists and corresponding indications 160 | Service 87 - Call control on EPS PDN connection by USIM 161 | Service 89 - eCall Data 162 | Service 90 - Operator CSG Lists and corresponding indications 163 | Service 93 - Communication Control for IMS by USIM 164 | Service 94 - Extended Terminal Applications 165 | Service 122 - 5GS Mobility Management Information 166 | Service 123 - 5G Security Parameters 167 | Service 124 - Subscription identifier privacy support 168 | Service 126 - UAC Access Identities support 169 | 170 | ePDGId: 171 | Not available 172 | 173 | ePDGSelection: 174 | ffffffffffff # unused 175 | ffffffffffff # unused 176 | ffffffffffff # unused 177 | ffffffffffff # unused 178 | 179 | P-CSCF: 180 | Not available 181 | Not available 182 | Not available 183 | Not available 184 | Not available 185 | Not available 186 | Not available 187 | Not available 188 | 189 | Home Network Domain Name: Not available 190 | IMS private user identity: Not available 191 | IMS public user identity: 192 | Not available 193 | Not available 194 | Not available 195 | Not available 196 | Not available 197 | Not available 198 | Not available 199 | Not available 200 | 201 | UICC IARI: 202 | Not available 203 | Not available 204 | Not available 205 | Not available 206 | Not available 207 | Not available 208 | Not available 209 | Not available 210 | 211 | ISIM Service Table: 190200 212 | Service 1 - P-CSCF address 213 | Service 4 - GBA-based Local Key Establishment Mechanism 214 | Service 5 - Support of P-CSCF discovery for IMS Local Break Out 215 | Service 10 - Support of UICC access to IMS 216 | 217 | Done ! 218 | 219 | -------------------------------------------------------------------------------- /pysim-testdata/sysmoUSIM-SJS1.data: -------------------------------------------------------------------------------- 1 | MCC=001 2 | MNC=01 3 | ICCID=1122334455667788990 4 | KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 5 | OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 6 | IMSI=001010000000102 7 | MSISDN=+77776336143 8 | ADM=55538407 9 | -------------------------------------------------------------------------------- /pysim-testdata/sysmoUSIM-SJS1.ok: -------------------------------------------------------------------------------- 1 | Using PC/SC reader interface 2 | Reading ... 3 | Autodetected card type: sysmoUSIM-SJS1 4 | ICCID: 1122334455667788990 5 | IMSI: 001010000000102 6 | GID1: ffffffffffffffffffff 7 | GID2: ffffffffffffffffffff 8 | SMSP: ffffffffffffffffffffffffffffffffffffffffffffffffe1ffffffffffffffffffffffff0581005155f5ffffffffffff000000 9 | SPN: Magic 10 | Show in HPLMN: True 11 | Hide in OPLMN: True 12 | PLMNsel: 00f110ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 13 | PLMNwAcT: 14 | 00f110ffff # MCC: 001 MNC: 01 AcT: UTRAN, E-UTRAN WB-S1, E-UTRAN NB-S1, NG-RAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT 15 | ffffff0000 # unused 16 | ffffff0000 # unused 17 | ffffff0000 # unused 18 | ffffff0000 # unused 19 | ffffff0000 # unused 20 | ffffff0000 # unused 21 | ffffff0000 # unused 22 | ffffff0000 # unused 23 | ffffff0000 # unused 24 | ffffff0000 # unused 25 | ffffff0000 # unused 26 | 27 | OPLMNwAcT: 28 | 00f110ffff # MCC: 001 MNC: 01 AcT: UTRAN, E-UTRAN WB-S1, E-UTRAN NB-S1, NG-RAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT 29 | ffffff0000 # unused 30 | ffffff0000 # unused 31 | ffffff0000 # unused 32 | ffffff0000 # unused 33 | ffffff0000 # unused 34 | ffffff0000 # unused 35 | ffffff0000 # unused 36 | ffffff0000 # unused 37 | ffffff0000 # unused 38 | ffffff0000 # unused 39 | ffffff0000 # unused 40 | 41 | HPLMNAcT: 42 | 00f110ffff # MCC: 001 MNC: 01 AcT: UTRAN, E-UTRAN WB-S1, E-UTRAN NB-S1, NG-RAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT 43 | ffffff0000 # unused 44 | ffffff0000 # unused 45 | ffffff0000 # unused 46 | ffffff0000 # unused 47 | ffffff0000 # unused 48 | ffffff0000 # unused 49 | ffffff0000 # unused 50 | ffffff0000 # unused 51 | ffffff0000 # unused 52 | ffffff0000 # unused 53 | ffffff0000 # unused 54 | 55 | ACC: 0008 56 | MSISDN (NPI=1 ToN=1): +77776336143 57 | Administrative data: 00000002 58 | MS operation mode: normal 59 | Ciphering Indicator: disabled 60 | SIM Service Table: ff3fffff3f003f1ff00c00c0f00000 61 | Service 1 - CHV1 disable function 62 | Service 2 - Abbreviated Dialling Numbers (ADN) 63 | Service 3 - Fixed Dialling Numbers (FDN) 64 | Service 4 - Short Message Storage (SMS) 65 | Service 5 - Advice of Charge (AoC) 66 | Service 6 - Capability Configuration Parameters (CCP) 67 | Service 7 - PLMN selector 68 | Service 8 - RFU 69 | Service 9 - MSISDN 70 | Service 10 - Extension1 71 | Service 11 - Extension2 72 | Service 12 - SMS Parameters 73 | Service 13 - Last Number Dialled (LND) 74 | Service 14 - Cell Broadcast Message Identifier 75 | Service 17 - Service Provider Name 76 | Service 18 - Service Dialling Numbers (SDN) 77 | Service 19 - Extension3 78 | Service 20 - RFU 79 | Service 21 - VGCS Group Identifier List (EFVGCS and EFVGCSS) 80 | Service 22 - VBS Group Identifier List (EFVBS and EFVBSS) 81 | Service 23 - enhanced Multi-Level Precedence and Pre-emption Service 82 | Service 24 - Automatic Answer for eMLPP 83 | Service 25 - Data download via SMS-CB 84 | Service 26 - Data download via SMS-PP 85 | Service 27 - Menu selection 86 | Service 28 - Call control 87 | Service 29 - Proactive SIM 88 | Service 30 - Cell Broadcast Message Identifier Ranges 89 | Service 31 - Barred Dialling Numbers (BDN) 90 | Service 32 - Extension4 91 | Service 33 - De-personalization Control Keys 92 | Service 34 - Co-operative Network List 93 | Service 35 - Short Message Status Reports 94 | Service 36 - Network's indication of alerting in the MS 95 | Service 37 - Mobile Originated Short Message control by SIM 96 | Service 38 - GPRS 97 | Service 49 - MExE 98 | Service 50 - Reserved and shall be ignored 99 | Service 51 - PLMN Network Name 100 | Service 52 - Operator PLMN List 101 | Service 53 - Mailbox Dialling Numbers 102 | Service 54 - Message Waiting Indication Status 103 | Service 57 - Multimedia Messaging Service (MMS) 104 | Service 58 - Extension 8 105 | Service 59 - MMS User Connectivity Parameters 106 | 107 | FPLMN: 108 | 62f201 # MCC: 262 MNC: 10 109 | 62f202 # MCC: 262 MNC: 20 110 | 62f203 # MCC: 262 MNC: 30 111 | 62f207 # MCC: 262 MNC: 70 112 | 113 | USIM Service Table: 9e6b1dfc67f6580000 114 | Service 2 - Fixed Dialling Numbers (FDN) 115 | Service 3 - Extension 2 116 | Service 4 - Service Dialling Numbers (SDN) 117 | Service 5 - Extension3 118 | Service 8 - Outgoing Call Information (OCI and OCT) 119 | Service 9 - Incoming Call Information (ICI and ICT) 120 | Service 10 - Short Message Storage (SMS) 121 | Service 12 - Short Message Service Parameters (SMSP) 122 | Service 14 - Capability Configuration Parameters 2 (CCP2) 123 | Service 15 - Cell Broadcast Message Identifier 124 | Service 17 - Group Identifier Level 1 125 | Service 19 - Service Provider Name 126 | Service 20 - User controlled PLMN selector with Access Technology 127 | Service 21 - MSISDN 128 | Service 27 - GSM Access 129 | Service 28 - Data download via SMS-PP 130 | Service 29 - Data download via SMS-CB 131 | Service 30 - Call Control by USIM 132 | Service 31 - MO-SMS Control by USIM 133 | Service 32 - RUN AT COMMAND command 134 | Service 33 - shall be set to 1 135 | Service 34 - Enabled Services Table 136 | Service 35 - APN Control List (ACL) 137 | Service 38 - GSM security context 138 | Service 39 - CPBCCH Information 139 | Service 42 - Operator controlled PLMN selector with Access Technology 140 | Service 43 - HPLMN selector with Access Technology 141 | Service 45 - PLMN Network Name 142 | Service 46 - Operator PLMN List 143 | Service 47 - Mailbox Dialling Numbers 144 | Service 48 - Message Waiting Indication Status 145 | Service 52 - Multimedia Messaging Service (MMS) 146 | Service 53 - Extension 8 147 | Service 55 - MMS User Connectivity Parameters 148 | 149 | Done ! 150 | 151 | -------------------------------------------------------------------------------- /pysim-testdata/sysmosim-gr1.data: -------------------------------------------------------------------------------- 1 | MCC=001 2 | MNC=01 3 | ICCID=1122334455667788990 4 | KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 5 | OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 6 | IMSI=001010000000102 7 | ADM=DDDDDDDD 8 | -------------------------------------------------------------------------------- /pysim-testdata/sysmosim-gr1.ok: -------------------------------------------------------------------------------- 1 | Using PC/SC reader interface 2 | Reading ... 3 | Autodetected card type: sysmosim-gr1 4 | ICCID: 1122334455667788990 5 | IMSI: 001010000000102 6 | GID1: Can't read file -- SW match failed! Expected 9000 and got 9404. 7 | GID2: Can't read file -- SW match failed! Expected 9000 and got 9404. 8 | SMSP: ffffffffffffffffffffffffe1ffffffffffffffffffffffff0581005155f5ffffffffffff000000 9 | SPN: Not available 10 | Show in HPLMN: False 11 | Hide in OPLMN: False 12 | PLMNsel: 00f110ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff 13 | PLMNwAcT: Can't read file -- SW match failed! Expected 9000 and got 9404. 14 | OPLMNwAcT: Can't read file -- SW match failed! Expected 9000 and got 9404. 15 | HPLMNAcT: Can't read file -- SW match failed! Expected 9000 and got 9404. 16 | ACC: 0008 17 | MSISDN: Not available 18 | Administrative data: 000000 19 | MS operation mode: normal 20 | Ciphering Indicator: disabled 21 | SIM Service Table: ff3fff0f0f0000030000 22 | Service 1 - CHV1 disable function 23 | Service 2 - Abbreviated Dialling Numbers (ADN) 24 | Service 3 - Fixed Dialling Numbers (FDN) 25 | Service 4 - Short Message Storage (SMS) 26 | Service 5 - Advice of Charge (AoC) 27 | Service 6 - Capability Configuration Parameters (CCP) 28 | Service 7 - PLMN selector 29 | Service 8 - RFU 30 | Service 9 - MSISDN 31 | Service 10 - Extension1 32 | Service 11 - Extension2 33 | Service 12 - SMS Parameters 34 | Service 13 - Last Number Dialled (LND) 35 | Service 14 - Cell Broadcast Message Identifier 36 | Service 17 - Service Provider Name 37 | Service 18 - Service Dialling Numbers (SDN) 38 | Service 19 - Extension3 39 | Service 20 - RFU 40 | Service 21 - VGCS Group Identifier List (EFVGCS and EFVGCSS) 41 | Service 22 - VBS Group Identifier List (EFVBS and EFVBSS) 42 | Service 23 - enhanced Multi-Level Precedence and Pre-emption Service 43 | Service 24 - Automatic Answer for eMLPP 44 | Service 25 - Data download via SMS-CB 45 | Service 26 - Data download via SMS-PP 46 | Service 27 - Menu selection 47 | Service 28 - Call control 48 | Service 33 - De-personalization Control Keys 49 | Service 34 - Co-operative Network List 50 | Service 35 - Short Message Status Reports 51 | Service 36 - Network's indication of alerting in the MS 52 | Service 57 - Multimedia Messaging Service (MMS) 53 | Service 58 - Extension 8 54 | 55 | Done ! 56 | 57 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyscard 2 | pyserial 3 | pytlv 4 | cmd2>=1.5 5 | jsonpath-ng 6 | construct>=2.9.51 7 | bidict 8 | gsm0338 9 | pyyaml>=5.1 10 | termcolor 11 | colorlog 12 | pycryptodomex 13 | packaging 14 | git+https://github.com/hologram-io/smpp.pdu 15 | -------------------------------------------------------------------------------- /scripts/deactivate-5g.script: -------------------------------------------------------------------------------- 1 | # script to be used with pySim-shell.py which is part of the Osmocom pysim package, 2 | # found at https://osmocom.org/projects/pysim/wiki 3 | set echo true 4 | 5 | # this script will deactivate all 5G related services and files. This can be used 6 | # in case you do not wish to use any 5G services, or you do not wish to configure 7 | # the 5G specific files on the USIM card. The card will then behave like a 3G USIM 8 | # without any 5G capability, using the default fall-back mechanisms specified by 3GPP. 9 | 10 | # TODO: add your card-specific ADM pin at the end of the verify_adm line below 11 | verify_adm 12 | 13 | # deactivate any 5G related services in EF.UST 14 | select ADF.USIM 15 | select EF.UST 16 | ust_service_deactivate 122 17 | ust_service_deactivate 123 18 | ust_service_deactivate 124 19 | ust_service_deactivate 125 20 | ust_service_deactivate 126 21 | ust_service_deactivate 127 22 | ust_service_deactivate 129 23 | ust_service_deactivate 130 24 | ust_service_deactivate 132 25 | ust_service_deactivate 133 26 | ust_service_deactivate 134 27 | ust_service_deactivate 135 28 | 29 | # deactivate all files in EF.5GS 30 | select ADF.USIM 31 | select DF.5GS 32 | 33 | select EF.5GAUTHKEYS 34 | deactivate_file 35 | 36 | select EF.5GS3GPPLOCI 37 | deactivate_file 38 | 39 | select EF.5GSN3GPPNSC 40 | deactivate_file 41 | 42 | select EF.5GSN3GPPLOCI 43 | deactivate_file 44 | 45 | select EF.5GS3GPPNSC 46 | deactivate_file 47 | 48 | # only exists on sysmoISIM-SJA2v2 49 | select EF.OPL5G 50 | deactivate_file 51 | 52 | select EF.Routing_Indicator 53 | deactivate_file 54 | 55 | select EF.SUCI_Calc_Info 56 | deactivate_file 57 | 58 | select EF.SUPI_NAI 59 | deactivate_file 60 | 61 | # only exists on sysmoISIM-SJA2v2 62 | select EF.TN3GPPSNN 63 | deactivate_file 64 | 65 | select EF.UAC_AIC 66 | deactivate_file 67 | 68 | # only exists on sysmoISIM-SJA2v2 69 | select EF.URSP 70 | deactivate_file 71 | -------------------------------------------------------------------------------- /scripts/deactivate-ims.script: -------------------------------------------------------------------------------- 1 | # script to be used with pySim-shell.py which is part of the Osmocom pysim package, 2 | # found at https://osmocom.org/projects/pysim/wiki 3 | set echo true 4 | 5 | # this script will deactivate all IMS related services and files. This can be used 6 | # in case you do not wish to use any IMS services, or you do not wish to configure 7 | # the IMS specific files on the USIM/ISIM cards. The card will then behave like a 3G USIM 8 | # without any IMS capability, using the default fall-back mechanisms specified by 3GPP. 9 | 10 | # TODO: add your card-specific ADM pin at the end of the verify_adm line below 11 | verify_adm 12 | 13 | # deactivate any IMS related services in EF.UST 14 | select ADF.USIM 15 | select EF.UST 16 | ust_service_deactivate 93 17 | ust_service_deactivate 95 18 | ust_service_deactivate 104 19 | ust_service_deactivate 105 20 | ust_service_deactivate 106 21 | ust_service_deactivate 107 22 | ust_service_deactivate 108 23 | ust_service_deactivate 109 24 | ust_service_deactivate 110 25 | ust_service_deactivate 112 26 | ust_service_deactivate 114 27 | ust_service_deactivate 115 28 | ust_service_deactivate 118 29 | ust_service_deactivate 120 30 | ust_service_deactivate 131 31 | ust_service_deactivate 134 32 | 33 | # deactivate all IMS related files in ADF.USIM 34 | select ADF.USIM 35 | 36 | select EF.UICCIARI 37 | deactivate_file 38 | 39 | select EF.ePDGId 40 | deactivate_file 41 | 42 | select EF.ePDGSelection 43 | deactivate_file 44 | 45 | select EF.ePDGIdEm 46 | deactivate_file 47 | 48 | select EF.ePDGSelectionEm 49 | deactivate_file 50 | 51 | select EF.FromPreferred 52 | deactivate_file 53 | 54 | select EF.IMSConfigData 55 | deactivate_file 56 | 57 | select EF.3GPPPSDATAOFF 58 | deactivate_file 59 | 60 | select EF.3GPPPSDATAOFFservicelist 61 | deactivate_file 62 | 63 | select EF.XCAPConfigData 64 | deactivate_file 65 | 66 | select EF.MuDMiDConfigData 67 | deactivate_file 68 | 69 | echo "Please make sure to manually disable the ISIM applet as described in the end of the script" 70 | # you can currently only manually do this via GlobalPlatformPro or some other tool using 71 | # java -jar ./gp.jar --key-enc KIC1 --key-mac KID1 --key-dek KIK1 --lock-applet A0000000871004FFFFFFFF8907090000 72 | # (substituting KIC1/KID1/KIK1 with the card-specific keys, of course) 73 | 74 | quit 75 | -------------------------------------------------------------------------------- /scripts/sysmoISIM-SJA2/dump-auth-cfg.pysim: -------------------------------------------------------------------------------- 1 | # script to be used with pySim-shell.py which is part of the Osmocom pysim package, 2 | # found at https://osmocom.org/projects/pysim/wiki 3 | set echo true 4 | 5 | # TODO: add your card-specific ADM pin at the end of the verify_adm line below 6 | verify_adm 7 | 8 | select DF.SYSTEM 9 | 10 | # Milenage configuration (constants) 11 | select EF.MILENAGE_CFG 12 | read_binary_decoded 13 | 14 | # 2G authentication kay / algorithm 15 | select EF.SIM_AUTH_KEY 16 | read_binary_decoded 17 | 18 | # OTA keys 19 | #select EF.0348_KEY 20 | #read_records_decoded 21 | 22 | select ADF.USIM 23 | # USIM authentication key / algoritmh in 3G security context 24 | select EF.USIM_AUTH_KEY 25 | read_binary_decoded 26 | # USIM authentication key / algorithm in 2G security context 27 | select EF.USIM_AUTH_KEY_2G 28 | read_binary_decoded 29 | # USIM SQN numbers 30 | select EF.USIM_SQN 31 | read_binary_decoded 32 | 33 | select ADF.ISIM 34 | # ISIM authentication key / algorithm 35 | select EF.ISIM_AUTH_KEY 36 | read_binary_decoded 37 | # ISIM SQN numbers 38 | select EF.ISIM_SQN 39 | read_binary_decoded 40 | 41 | quit 42 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | long_description = file: README.md 3 | long_description_content_type = text/markdown -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='pySim', 5 | version='1.0', 6 | packages=['pySim', 'pySim.legacy', 'pySim.transport', 'pySim.apdu', 'pySim.apdu_source'], 7 | url='https://osmocom.org/projects/pysim/wiki', 8 | license='GPLv2', 9 | author_email='simtrace@lists.osmocom.org', 10 | description='Tools related to SIM/USIM/ISIM cards', 11 | install_requires=[ 12 | "pyscard", 13 | "pyserial", 14 | "pytlv", 15 | "cmd2 >= 1.5.0", 16 | "jsonpath-ng", 17 | "construct >= 2.9.51", 18 | "bidict", 19 | "gsm0338", 20 | "pyyaml >= 5.1", 21 | "termcolor", 22 | "colorlog", 23 | "pycryptodomex", 24 | "packaging", 25 | "smpp.pdu @ git+https://github.com/hologram-io/smpp.pdu", 26 | ], 27 | scripts=[ 28 | 'pySim-prog.py', 29 | 'pySim-read.py', 30 | 'pySim-shell.py', 31 | 'pySim-trace.py', 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /tests/pySim-prog_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Utility to verify the functionality of pySim-prog.py 4 | # 5 | # (C) 2018 by Sysmocom s.f.m.c. GmbH 6 | # All Rights Reserved 7 | # 8 | # Author: Philipp Maier 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | PYSIM_PROG=../pySim-prog.py 24 | PYSIM_READ=../pySim-read.py 25 | TEMPFILE=temp.tmp 26 | PYTHON=python3 27 | 28 | export PYSIM_INTEGRATION_TEST=1 29 | set -e 30 | 31 | echo "pySim-prog_test - a test program to test pySim-prog.py" 32 | echo "======================================================" 33 | 34 | # Generate a list of the cards we expect to see by checking which .ok files 35 | # are present 36 | function gen_card_list { 37 | N_CARDS=0 38 | 39 | echo "Expecting to see the following cards:" 40 | 41 | for I in *.data ; do 42 | CARD_NAMES[$N_CARDS]=${I%.*} 43 | CARD_SEEN[$N_CARDS]=0 44 | N_CARDS=$((N_CARDS+1)) 45 | done 46 | 47 | for I in $(seq 0 $((N_CARDS-1))); do 48 | echo ${CARD_NAMES[$I]} 49 | done 50 | } 51 | 52 | # Increment counter in card list for a specified card name (type) 53 | function inc_card_list { 54 | CARD_NAME=$1 55 | for I in $(seq 0 $((N_CARDS-1))); do 56 | if [ $CARD_NAME = ${CARD_NAMES[$I]} ]; then 57 | CARD_SEEN[$I]=$((${CARD_NAMES[$I]}+1)) 58 | fi 59 | done 60 | } 61 | 62 | # Check the card list, each card must be seen exactly one times 63 | function check_card_list { 64 | for I in $(seq 0 $((N_CARDS-1))); do 65 | if [ ${CARD_SEEN[$I]} -ne 1 ]; then 66 | echo "Error: Card ${CARD_NAMES[$I]} seen ${CARD_SEEN[$I]} times!" 67 | exit 1 68 | fi 69 | done 70 | 71 | echo "All cards seen -- everything ok!" 72 | } 73 | 74 | # Verify the contents of a card by reading them and then diffing against the 75 | # previously created .ok file 76 | function check_card { 77 | TERMINAL=$1 78 | CARD_NAME=$2 79 | echo "Verifying card ..." 80 | stat ./$CARD_NAME.ok > /dev/null 81 | $PYTHON $PYSIM_READ -p $TERMINAL > $TEMPFILE 82 | set +e 83 | CARD_DIFF=$(diff $TEMPFILE ./$CARD_NAME.ok) 84 | set -e 85 | 86 | if [ "$CARD_DIFF" != "" ]; then 87 | echo "Card contents do not match the test data:" 88 | echo "Expected: $CARD_NAME.ok" 89 | echo "------------8<------------" 90 | cat "$CARD_NAME.ok" 91 | echo "------------8<------------" 92 | echo "Got:" 93 | echo "------------8<------------" 94 | cat $TEMPFILE 95 | echo "------------8<------------" 96 | rm *.tmp 97 | exit 1 98 | fi 99 | 100 | inc_card_list $CARD_NAME 101 | 102 | echo "Card contents match the test data -- success!" 103 | rm $TEMPFILE 104 | } 105 | 106 | # Read out the card using pysim-read and store the result as .ok file. This 107 | # data will be used later in order to verify the results of our write tests. 108 | function gen_ok_file { 109 | TERMINAL=$1 110 | CARD_NAME=$2 111 | $PYTHON $PYSIM_READ -p $TERMINAL > "$CARD_NAME.ok" 112 | echo "Generated file: $CARD_NAME.ok" 113 | echo "------------8<------------" 114 | cat "$CARD_NAME.ok" 115 | echo "------------8<------------" 116 | } 117 | 118 | # Find out the type (card name) of the card that is installed in the specified 119 | # reader 120 | function probe_card { 121 | TERMINAL=$1 122 | RESULT=$(timeout 5 $PYSIM_PROG -p $TERMINAL -T | cut -d ":" -f 2 | tail -n 1 | xargs) 123 | echo $RESULT 124 | } 125 | 126 | # Read out all cards and store the results as .ok files 127 | function gen_ok_files { 128 | echo "== OK FILE GENERATION ==" 129 | for I in $(seq 0 $((N_TERMINALS-1))); do 130 | echo "Probing card in terminal #$I" 131 | CARD_NAME=$(probe_card $I) 132 | if [ -z "$CARD_NAME" ]; then 133 | echo "Error: Unresponsive card!" 134 | exit 1 135 | fi 136 | echo "Card is of type: $CARD_NAME" 137 | gen_ok_file $I $CARD_NAME 138 | done 139 | } 140 | 141 | # Execute tests. Each card is programmed and the contents are checked 142 | # afterwards. 143 | function run_test { 144 | for I in $(seq 0 $((N_TERMINALS-1))); do 145 | echo "== EXECUTING TEST ==" 146 | echo "Probing card in terminal #$I" 147 | CARD_NAME=$(probe_card $I) 148 | if [ -z "$CARD_NAME" ]; then 149 | echo "Error: Unresponsive card!" 150 | exit 1 151 | fi 152 | echo "Card is of type: $CARD_NAME" 153 | 154 | # Make sure some default data is set 155 | MCC=001 156 | MNC=01 157 | ICCID=1122334455667788990 158 | KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 159 | OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 160 | IMSI=001010000000001 161 | MSISDN=6766266 162 | ADM=00000000 163 | ADM_HEX="" 164 | ADM_OPT="-a" 165 | 166 | source "$CARD_NAME.data" 167 | if [ -n "$ADM_HEX" ]; then 168 | ADM_OPT="-A" 169 | ADM=$ADM_HEX 170 | fi 171 | $PYTHON $PYSIM_PROG -p $I -t $CARD_NAME -o $OPC -k $KI -x $MCC -y $MNC -i $IMSI -s $ICCID --msisdn $MSISDN $ADM_OPT $ADM 172 | check_card $I $CARD_NAME 173 | echo "" 174 | done 175 | } 176 | 177 | function usage { 178 | echo "Options:" 179 | echo "-n: number of card terminals" 180 | echo "-o: generate .ok files" 181 | } 182 | 183 | # Make sure that the pathes to the python scripts always work, regardless from 184 | # where the script is called. 185 | CURDIR=$PWD 186 | SCRIPTDIR=$(dirname $0) 187 | cd $SCRIPTDIR 188 | PYSIM_PROG=$(realpath $PYSIM_PROG) 189 | PYSIM_READ=$(realpath $PYSIM_READ) 190 | cd $CURDIR 191 | 192 | OPT_N_TERMINALS=0 193 | OPT_GEN_OK_FILES=0 194 | while getopts ":hon:" OPT; do 195 | case $OPT in 196 | h) 197 | usage 198 | exit 0 199 | ;; 200 | o) 201 | OPT_GEN_OK_FILES=1 202 | ;; 203 | n) 204 | OPT_N_TERMINALS=$OPTARG 205 | ;; 206 | \?) 207 | echo "Invalid option: -$OPTARG" >&2 208 | exit 1 209 | ;; 210 | esac 211 | done 212 | 213 | N_TERMINALS=$OPT_N_TERMINALS 214 | 215 | # Generate a list of available cards, if no explicit reader number is given 216 | # then the number of cards will be used as reader number. 217 | gen_card_list 218 | if [ $N_TERMINALS -eq 0 ]; then 219 | N_TERMINALS=$N_CARDS 220 | fi 221 | echo "Number of card terminals installed: $N_TERMINALS" 222 | echo "" 223 | 224 | if [ $OPT_GEN_OK_FILES -eq 1 ]; then 225 | gen_ok_files 226 | exit 0 227 | else 228 | run_test 229 | check_card_list 230 | exit 0 231 | fi 232 | -------------------------------------------------------------------------------- /tests/pySim-trace_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Utility to verify the functionality of pySim-trace.py 4 | # 5 | # (C) 2023 by Sysmocom s.f.m.c. GmbH 6 | # All Rights Reserved 7 | # 8 | # Author: Philipp Maier 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 2 of the License, or 13 | # (at your option) any later version. 14 | # 15 | # This program is distributed in the hope that it will be useful, 16 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | # GNU General Public License for more details. 19 | # 20 | # You should have received a copy of the GNU General Public License 21 | # along with this program. If not, see . 22 | 23 | PYSIM_TRACE=../pySim-trace.py 24 | GSMTAP_TRACE=pySim-trace_test_gsmtap.pcapng 25 | TEMPFILE=temp.tmp 26 | 27 | export PYSIM_INTEGRATION_TEST=1 28 | 29 | echo "pySim-trace_test - a test program to test pySim-trace.py" 30 | echo "========================================================" 31 | 32 | function usage { 33 | echo "Options:" 34 | echo "-o: generate .ok file" 35 | } 36 | 37 | function gen_ok_file { 38 | $PYSIM_TRACE gsmtap-pyshark-pcap -f $GSMTAP_TRACE > $GSMTAP_TRACE.ok 39 | echo "Generated file: $GSMTAP_TRACE.ok" 40 | echo "------------8<------------" 41 | cat $GSMTAP_TRACE.ok 42 | echo "------------8<------------" 43 | } 44 | 45 | function run_test { 46 | $PYSIM_TRACE gsmtap-pyshark-pcap -f $GSMTAP_TRACE | tee $TEMPFILE 47 | if [ ${PIPESTATUS[0]} -ne 0 ]; then 48 | echo "" 49 | echo "========================================================" 50 | echo "Testrun with $GSMTAP_TRACE failed (exception)." 51 | rm -f $TEMPFILE 52 | exit 1 53 | fi 54 | 55 | DIFF=`diff $GSMTAP_TRACE.ok $TEMPFILE` 56 | if ! [ -z "$DIFF" ]; then 57 | echo "Testrun with $GSMTAP_TRACE failed (unexpected output)." 58 | echo "------------8<------------" 59 | diff $GSMTAP_TRACE.ok $TEMPFILE 60 | echo "------------8<------------" 61 | rm -f $TEMPFILE 62 | exit 1 63 | fi 64 | 65 | echo "" 66 | echo "========================================================" 67 | echo "trace parsed without problems -- everything ok!" 68 | rm -f $TEMPFILE 69 | } 70 | 71 | OPT_GEN_OK_FILE=0 72 | while getopts ":ho" OPT; do 73 | case $OPT in 74 | h) 75 | usage 76 | exit 0 77 | ;; 78 | o) 79 | OPT_GEN_OK_FILE=1 80 | ;; 81 | \?) 82 | echo "Invalid option: -$OPTARG" >&2 83 | exit 1 84 | ;; 85 | esac 86 | done 87 | 88 | if [ $OPT_GEN_OK_FILE -eq 1 ]; then 89 | gen_ok_file 90 | exit 0 91 | else 92 | run_test 93 | exit 0 94 | fi 95 | -------------------------------------------------------------------------------- /tests/test_apdu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | from pySim.utils import h2b, b2h 5 | from pySim.construct import filter_dict 6 | from pySim.apdu import Apdu 7 | from pySim.apdu.ts_31_102 import UsimAuthenticateEven 8 | 9 | class TestApdu(unittest.TestCase): 10 | def test_successful(self): 11 | apdu = Apdu('00a40400023f00', '9000') 12 | self.assertEqual(apdu.successful, True) 13 | apdu = Apdu('00a40400023f00', '6733') 14 | self.assertEqual(apdu.successful, False) 15 | 16 | def test_successful_method(self): 17 | """Test overloading of the success property with a custom method.""" 18 | class SwApdu(Apdu): 19 | def _is_success(self): 20 | return False 21 | apdu = SwApdu('00a40400023f00', '9000') 22 | self.assertEqual(apdu.successful, False) 23 | 24 | # TODO: Tests for TS 102 221 / 31.102 ApduCommands 25 | 26 | class TestUsimAuth(unittest.TestCase): 27 | """Test decoding of the rather complex USIM AUTHENTICATE command.""" 28 | def test_2g(self): 29 | apdu = ('80880080' + '09' + '080001020304050607', 30 | '04a0a1a2a308b0b1b2b3b4b5b6b79000') 31 | res = { 32 | 'cmd': {'p1': 0, 'p2': {'scope': 'df_adf_specific', 'authentication_context': 'gsm'}, 33 | 'body': {'rand': '0001020304050607', 'autn': None}}, 34 | 'rsp': {'body': {'sres': 'a0a1a2a3', 'kc': 'b0b1b2b3b4b5b6b7'}} 35 | } 36 | u = UsimAuthenticateEven(apdu[0], apdu[1]) 37 | d = filter_dict(u.to_dict()) 38 | self.assertEqual(d, res) 39 | 40 | def test_3g(self): 41 | apdu = ('80880081' + '12' + '080001020304050607081011121314151617', 42 | 'DB' + '08' + 'a0a1a2a3a4a5a6a7' + 43 | '10' + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + 44 | '10' + 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf' + '9000') 45 | res = { 46 | 'cmd': {'p1': 0, 'p2': {'scope': 'df_adf_specific', 'authentication_context': 'umts'}, 47 | 'body': {'rand': '0001020304050607', 'autn': '1011121314151617'}}, 48 | 'rsp': {'body': {'tag': 219, 49 | 'body': { 50 | 'res': 'a0a1a2a3a4a5a6a7', 51 | 'ck': 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf', 52 | 'ik': 'c0c1c2c3c4c5c6c7c8c9cacbcccdcecf', 53 | 'kc': None 54 | } 55 | } 56 | } 57 | } 58 | u = UsimAuthenticateEven(apdu[0], apdu[1]) 59 | d = filter_dict(u.to_dict()) 60 | self.assertEqual(d, res) 61 | 62 | def test_3g_sync(self): 63 | apdu = ('80880081' + '12' + '080001020304050607081011121314151617', 64 | 'DC' + '08' + 'a0a1a2a3a4a5a6a7' + '9000') 65 | res = { 66 | 'cmd': {'p1': 0, 'p2': {'scope': 'df_adf_specific', 'authentication_context': 'umts'}, 67 | 'body': {'rand': '0001020304050607', 'autn': '1011121314151617'}}, 68 | 'rsp': {'body': {'tag': 220, 'body': {'auts': 'a0a1a2a3a4a5a6a7' }}} 69 | } 70 | u = UsimAuthenticateEven(apdu[0], apdu[1]) 71 | d = filter_dict(u.to_dict()) 72 | self.assertEqual(d, res) 73 | 74 | def test_vgcs(self): 75 | apdu = ('80880082' + '0E' + '04' + '00010203' + 76 | '01' + '10' + 77 | '08' + '2021222324252627', 78 | 'DB' + '10' + 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf' + '9000') 79 | res = { 80 | 'cmd': {'p1': 0, 'p2': {'scope': 'df_adf_specific', 'authentication_context': 'vgcs_vbs'}, 81 | 'body': { 'vk_id': '10', 'vservice_id': '00010203', 'vstk_rand': '2021222324252627'}}, 82 | 'rsp': {'body': {'vstk': 'b0b1b2b3b4b5b6b7b8b9babbbcbdbebf'}} 83 | } 84 | u = UsimAuthenticateEven(apdu[0], apdu[1]) 85 | d = filter_dict(u.to_dict()) 86 | self.assertEqual(d, res) 87 | 88 | 89 | 90 | if __name__ == "__main__": 91 | unittest.main() 92 | -------------------------------------------------------------------------------- /tests/test_construct.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | from pySim.construct import * 5 | 6 | tests = [ 7 | ( b'\x80', 0x80 ), 8 | ( b'\x80\x01', 0x8001 ), 9 | ( b'\x80\x00\x01', 0x800001 ), 10 | ( b'\x80\x23\x42\x01', 0x80234201 ), 11 | ] 12 | 13 | class TestGreedyInt(unittest.TestCase): 14 | def test_GreedyInt_decoder(self): 15 | gi = GreedyInteger() 16 | for t in tests: 17 | self.assertEqual(gi.parse(t[0]), t[1]) 18 | def test_GreedyInt_encoder(self): 19 | gi = GreedyInteger() 20 | for t in tests: 21 | self.assertEqual(t[0], gi.build(t[1])) 22 | pass 23 | 24 | class TestUtils(unittest.TestCase): 25 | def test_filter_dict(self): 26 | inp = {'foo': 0xf00, '_bar' : 0xba5, 'baz': 0xba2 } 27 | out = {'foo': 0xf00, 'baz': 0xba2 } 28 | self.assertEqual(filter_dict(inp), out) 29 | 30 | def test_filter_dict_nested(self): 31 | inp = {'foo': 0xf00, 'nest': {'_bar' : 0xba5}, 'baz': 0xba2 } 32 | out = {'foo': 0xf00, 'nest': {}, 'baz': 0xba2 } 33 | self.assertEqual(filter_dict(inp), out) 34 | 35 | 36 | if __name__ == "__main__": 37 | unittest.main() 38 | -------------------------------------------------------------------------------- /tests/test_sms.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import unittest 4 | from pySim.utils import h2b, b2h 5 | from pySim.sms import * 6 | 7 | class Test_SMS_UDH(unittest.TestCase): 8 | def test_single_ie(self): 9 | udh, tail = UserDataHeader.fromBytes('027100') 10 | self.assertEqual(len(udh.ies), 1) 11 | ie = udh.ies[0] 12 | self.assertEqual(ie.iei, 0x71) 13 | self.assertEqual(ie.length, 0) 14 | self.assertEqual(ie.value, b'') 15 | self.assertEqual(tail, b'') 16 | 17 | def test_single_ie_tail(self): 18 | udh, tail = UserDataHeader.fromBytes('027100abcdef') 19 | self.assertEqual(len(udh.ies), 1) 20 | ie = udh.ies[0] 21 | self.assertEqual(ie.iei, 0x71) 22 | self.assertEqual(ie.length, 0) 23 | self.assertEqual(ie.value, b'') 24 | self.assertEqual(tail, b'\xab\xcd\xef') 25 | 26 | def test_single_ie_value(self): 27 | udh, tail = UserDataHeader.fromBytes('03710110') 28 | self.assertEqual(len(udh.ies), 1) 29 | ie = udh.ies[0] 30 | self.assertEqual(ie.iei, 0x71) 31 | self.assertEqual(ie.length, 1) 32 | self.assertEqual(ie.value, b'\x10') 33 | self.assertEqual(tail, b'') 34 | 35 | def test_two_ie_data_tail(self): 36 | udh, tail = UserDataHeader.fromBytes('0571007001ffabcd') 37 | self.assertEqual(len(udh.ies), 2) 38 | ie = udh.ies[0] 39 | self.assertEqual(ie.iei, 0x71) 40 | self.assertEqual(ie.length, 0) 41 | self.assertEqual(ie.value, b'') 42 | ie = udh.ies[1] 43 | self.assertEqual(ie.iei, 0x70) 44 | self.assertEqual(ie.length, 1) 45 | self.assertEqual(ie.value, b'\xff') 46 | self.assertEqual(tail, b'\xab\xcd') 47 | 48 | def test_toBytes(self): 49 | indata = h2b('0571007001ff') 50 | udh, tail = UserDataHeader.fromBytes(indata) 51 | encoded = udh.toBytes() 52 | self.assertEqual(encoded, indata) 53 | 54 | class Test_AddressField(unittest.TestCase): 55 | def test_fromBytes(self): 56 | encoded = h2b('0480214399') 57 | af, trailer = AddressField.fromBytes(encoded) 58 | self.assertEqual(trailer, b'\x99') 59 | self.assertEqual(af.ton, 'unknown') 60 | self.assertEqual(af.npi, 'unknown') 61 | self.assertEqual(af.digits, '1234') 62 | 63 | def test_fromBytes_odd(self): 64 | af, trailer = AddressField.fromBytes('038021f399') 65 | self.assertEqual(trailer, b'\x99') 66 | self.assertEqual(af.ton, 'unknown') 67 | self.assertEqual(af.npi, 'unknown') 68 | self.assertEqual(af.digits, '123') 69 | 70 | def test_toBytes(self): 71 | encoded = h2b('04802143') 72 | af, trailer = AddressField.fromBytes(encoded) 73 | self.assertEqual(af.toBytes(), encoded) 74 | 75 | def test_toBytes_odd(self): 76 | af = AddressField('12345', 'international', 'isdn_e164') 77 | encoded = af.toBytes() 78 | self.assertEqual(encoded, h2b('05912143f5')) 79 | 80 | 81 | class Test_SUBMIT(unittest.TestCase): 82 | def test_fromBytes(self): 83 | s = SMS_SUBMIT.fromBytes('550d0b911614261771f000f5a78c0b050423f423f40003010201424547494e3a56434152440d0a56455253494f4e3a322e310d0a4e3a4d650d0a54454c3b505245463b43454c4c3b564f4943453a2b36313431363237313137300d0a54454c3b484f4d453b564f4943453a2b36313339353337303437310d0a54454c3b574f524b3b564f4943453a2b36313339363734373031350d0a454e443a') 84 | self.assertEqual(s.tp_mti, 1) 85 | self.assertEqual(s.tp_rd, True) 86 | self.assertEqual(s.tp_vpf, 'relative') 87 | self.assertEqual(s.tp_rp, False) 88 | self.assertEqual(s.tp_udhi, True) 89 | self.assertEqual(s.tp_srr, False) 90 | self.assertEqual(s.tp_pid, 0) 91 | self.assertEqual(s.tp_dcs, 0xf5) 92 | self.assertEqual(s.tp_udl, 140) 93 | 94 | class Test_DELIVER(unittest.TestCase): 95 | def test_fromBytes(self): 96 | d = SMS_DELIVER.fromBytes('0408D0E5759A0E7FF6907090307513000824010101BB400101') 97 | self.assertEqual(d.tp_mti, 0) 98 | self.assertEqual(d.tp_mms, True) 99 | self.assertEqual(d.tp_lp, False) 100 | self.assertEqual(d.tp_rp, False) 101 | self.assertEqual(d.tp_udhi, False) 102 | self.assertEqual(d.tp_sri, False) 103 | self.assertEqual(d.tp_pid, 0x7f) 104 | self.assertEqual(d.tp_dcs, 0xf6) 105 | self.assertEqual(d.tp_udl, 8) 106 | -------------------------------------------------------------------------------- /tests/test_tlv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # (C) 2022 by Harald Welte 4 | # All Rights Reserved 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program. If not, see . 18 | 19 | import unittest 20 | from pySim.tlv import * 21 | 22 | class TestUtils(unittest.TestCase): 23 | def test_camel_to_snake(self): 24 | cases = [ 25 | ('CamelCase', 'camel_case'), 26 | ('CamelCaseUPPER', 'camel_case_upper'), 27 | ('Camel_CASE_underSCORE', 'camel_case_under_score'), 28 | ] 29 | for c in cases: 30 | self.assertEqual(camel_to_snake(c[0]), c[1]) 31 | 32 | def test_flatten_dict_lists(self): 33 | inp = [ 34 | { 'first': 1 }, 35 | { 'second': 2 }, 36 | { 'third': 3 }, 37 | ] 38 | out = { 'first': 1, 'second':2, 'third': 3} 39 | self.assertEqual(flatten_dict_lists(inp), out) 40 | 41 | def test_flatten_dict_lists_nodict(self): 42 | inp = [ 43 | { 'first': 1 }, 44 | { 'second': 2 }, 45 | { 'third': 3 }, 46 | 4, 47 | ] 48 | self.assertEqual(flatten_dict_lists(inp), inp) 49 | 50 | def test_flatten_dict_lists_nested(self): 51 | inp = {'top': [ 52 | { 'first': 1 }, 53 | { 'second': 2 }, 54 | { 'third': 3 }, 55 | ] } 56 | out = {'top': { 'first': 1, 'second':2, 'third': 3 } } 57 | self.assertEqual(flatten_dict_lists(inp), out) 58 | 59 | class TestTranscodable(unittest.TestCase): 60 | class XC_constr_class(Transcodable): 61 | _construct = Int8ub 62 | def __init__(self): 63 | super().__init__(); 64 | 65 | def test_XC_constr_class(self): 66 | """Transcodable derived class with _construct class variable""" 67 | xc = TestTranscodable.XC_constr_class() 68 | self.assertEqual(xc.from_bytes(b'\x23'), 35) 69 | self.assertEqual(xc.to_bytes(), b'\x23') 70 | 71 | class XC_constr_instance(Transcodable): 72 | def __init__(self): 73 | super().__init__(); 74 | self._construct = Int8ub 75 | 76 | def test_XC_constr_instance(self): 77 | """Transcodable derived class with _construct instance variable""" 78 | xc = TestTranscodable.XC_constr_instance() 79 | self.assertEqual(xc.from_bytes(b'\x23'), 35) 80 | self.assertEqual(xc.to_bytes(), b'\x23') 81 | 82 | class XC_method_instance(Transcodable): 83 | def __init__(self): 84 | super().__init__(); 85 | def _from_bytes(self, do): 86 | return ('decoded', do) 87 | def _to_bytes(self): 88 | return self.decoded[1] 89 | 90 | def test_XC_method_instance(self): 91 | """Transcodable derived class with _{from,to}_bytes() methods""" 92 | xc = TestTranscodable.XC_method_instance() 93 | self.assertEqual(xc.to_bytes(), b'') 94 | self.assertEqual(xc.from_bytes(b''), None) 95 | self.assertEqual(xc.from_bytes(b'\x23'), ('decoded', b'\x23')) 96 | self.assertEqual(xc.to_bytes(), b'\x23') 97 | 98 | class TestIE(unittest.TestCase): 99 | class MyIE(IE, tag=0x23, desc='My IE description'): 100 | _construct = Int8ub 101 | def to_ie(self): 102 | return self.to_bytes() 103 | 104 | def test_IE_empty(self): 105 | ie = TestIE.MyIE() 106 | self.assertEqual(ie.to_dict(), {'my_ie': None}) 107 | self.assertEqual(repr(ie), 'MyIE(None)') 108 | self.assertEqual(ie.is_constructed(), False) 109 | 110 | def test_IE_from_bytes(self): 111 | ie = TestIE.MyIE() 112 | ie.from_bytes(b'\x42') 113 | self.assertEqual(ie.to_dict(), {'my_ie': 66}) 114 | self.assertEqual(repr(ie), 'MyIE(66)') 115 | self.assertEqual(ie.is_constructed(), False) 116 | self.assertEqual(ie.to_bytes(), b'\x42') 117 | self.assertEqual(ie.to_ie(), b'\x42') 118 | 119 | if __name__ == "__main__": 120 | unittest.main() 121 | --------------------------------------------------------------------------------