├── .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://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 |
--------------------------------------------------------------------------------