blob: 448f2a0f2a8e708bc58b70baf19ed05cf2758ef8 [file] [log] [blame]
Daniel Willmannde07b952020-10-19 10:32:34 +02001#!/usr/bin/env python3
Sylvain Munaut76504e02010-12-07 00:24:32 +01002
3#
4# Utility to deal with sim cards and program the 'magic' ones easily
5#
6#
7# Part of the sim link code of inspired by pySimReader-Serial-src-v2
8#
9#
10# Copyright (C) 2009 Sylvain Munaut <tnt@246tNt.com>
11# Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>
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 <http://www.gnu.org/licenses/>.
25#
26
27import hashlib
28from optparse import OptionParser
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +010029import os
Sylvain Munaut76504e02010-12-07 00:24:32 +010030import random
31import re
32import sys
Philipp Maierc5b422e2019-08-30 11:41:02 +020033import traceback
Denis 'GNUtoo' Carikli79f5b602020-02-15 04:02:57 +070034import json
Philipp Maier2688ddf2022-12-16 13:36:42 +010035import csv
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +010036
Sylvain Munaut76504e02010-12-07 00:24:32 +010037from pySim.commands import SimCardCommands
Harald Welte6e0458d2021-04-03 11:52:37 +020038from pySim.transport import init_reader
Supreeth Herle4c306ab2020-03-18 11:38:00 +010039from pySim.cards import _cards_classes, card_detect
Harald Welte6e0458d2021-04-03 11:52:37 +020040from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn, dec_iccid
Robert Falkenbergd0505bd2021-02-24 14:06:18 +010041from pySim.ts_51_011 import EF, EF_AD
Philipp Maierc5b422e2019-08-30 11:41:02 +020042from pySim.card_handler import *
Philipp Maier7592eee2019-09-12 13:03:23 +020043from pySim.utils import *
Sylvain Munaut76504e02010-12-07 00:24:32 +010044
Harald Weltec91085e2022-02-10 18:05:45 +010045
Sylvain Munaut76504e02010-12-07 00:24:32 +010046def parse_options():
47
Harald Weltec91085e2022-02-10 18:05:45 +010048 parser = OptionParser(usage="usage: %prog [options]")
Sylvain Munaut76504e02010-12-07 00:24:32 +010049
Harald Weltec91085e2022-02-10 18:05:45 +010050 parser.add_option("-d", "--device", dest="device", metavar="DEV",
51 help="Serial Device for SIM access [default: %default]",
52 default="/dev/ttyUSB0",
53 )
54 parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD",
55 help="Baudrate used for SIM access [default: %default]",
56 default=9600,
57 )
58 parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC",
59 help="Which PC/SC reader number for SIM access",
60 default=None,
61 )
62 parser.add_option("--modem-device", dest="modem_dev", metavar="DEV",
63 help="Serial port of modem for Generic SIM Access (3GPP TS 27.007)",
64 default=None,
65 )
66 parser.add_option("--modem-baud", dest="modem_baud", type="int", metavar="BAUD",
67 help="Baudrate used for modem's port [default: %default]",
68 default=115200,
69 )
70 parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",
71 help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",
72 default=None,
73 )
74 parser.add_option("-t", "--type", dest="type",
75 help="Card type (user -t list to view) [default: %default]",
76 default="auto",
77 )
78 parser.add_option("-T", "--probe", dest="probe",
79 help="Determine card type",
80 default=False, action="store_true"
81 )
82 parser.add_option("-a", "--pin-adm", dest="pin_adm",
83 help="ADM PIN used for provisioning (overwrites default)",
84 )
85 parser.add_option("-A", "--pin-adm-hex", dest="pin_adm_hex",
86 help="ADM PIN used for provisioning, as hex string (16 characters long",
87 )
88 parser.add_option("-e", "--erase", dest="erase", action='store_true',
89 help="Erase beforehand [default: %default]",
90 default=False,
91 )
Sylvain Munaut76504e02010-12-07 00:24:32 +010092
Harald Weltec91085e2022-02-10 18:05:45 +010093 parser.add_option("-S", "--source", dest="source",
94 help="Data Source[default: %default]",
95 default="cmdline",
96 )
Harald Welte7f62cec2012-08-13 20:07:41 +020097
Harald Weltec91085e2022-02-10 18:05:45 +010098 # if mode is "cmdline"
99 parser.add_option("-n", "--name", dest="name",
100 help="Operator name [default: %default]",
101 default="Magic",
102 )
103 parser.add_option("-c", "--country", dest="country", type="int", metavar="CC",
104 help="Country code [default: %default]",
105 default=1,
106 )
107 parser.add_option("-x", "--mcc", dest="mcc", type="string",
108 help="Mobile Country Code [default: %default]",
109 default="901",
110 )
111 parser.add_option("-y", "--mnc", dest="mnc", type="string",
112 help="Mobile Network Code [default: %default]",
113 default="55",
114 )
115 parser.add_option("--mnclen", dest="mnclen", type="choice",
116 help="Length of Mobile Network Code [default: %default]",
Philipp Maier32c04342022-12-16 16:39:24 +0100117 default="auto",
118 choices=["2", "3", "auto"],
Harald Weltec91085e2022-02-10 18:05:45 +0100119 )
120 parser.add_option("-m", "--smsc", dest="smsc",
121 help="SMSC number (Start with + for international no.) [default: '00 + country code + 5555']",
122 )
123 parser.add_option("-M", "--smsp", dest="smsp",
124 help="Raw SMSP content in hex [default: auto from SMSC]",
125 )
Sylvain Munaut76504e02010-12-07 00:24:32 +0100126
Harald Weltec91085e2022-02-10 18:05:45 +0100127 parser.add_option("-s", "--iccid", dest="iccid", metavar="ID",
128 help="Integrated Circuit Card ID",
129 )
130 parser.add_option("-i", "--imsi", dest="imsi",
131 help="International Mobile Subscriber Identity",
132 )
133 parser.add_option("--msisdn", dest="msisdn",
134 help="Mobile Subscriber Integrated Services Digital Number",
135 )
136 parser.add_option("-k", "--ki", dest="ki",
137 help="Ki (default is to randomize)",
138 )
139 parser.add_option("-o", "--opc", dest="opc",
140 help="OPC (default is to randomize)",
141 )
142 parser.add_option("--op", dest="op",
143 help="Set OP to derive OPC from OP and KI",
144 )
145 parser.add_option("--acc", dest="acc",
146 help="Set ACC bits (Access Control Code). not all card types are supported",
147 )
148 parser.add_option("--opmode", dest="opmode", type="choice",
149 help="Set UE Operation Mode in EF.AD (Administrative Data)",
150 default=None,
151 choices=['{:02X}'.format(int(m)) for m in EF_AD.OP_MODE],
152 )
153 parser.add_option("--epdgid", dest="epdgid",
154 help="Set Home Evolved Packet Data Gateway (ePDG) Identifier. (Only FQDN format supported)",
155 )
156 parser.add_option("--epdgSelection", dest="epdgSelection",
157 help="Set PLMN for ePDG Selection Information. (Only Operator Identifier FQDN format supported)",
158 )
159 parser.add_option("--pcscf", dest="pcscf",
160 help="Set Proxy Call Session Control Function (P-CSCF) Address. (Only FQDN format supported)",
161 )
162 parser.add_option("--ims-hdomain", dest="ims_hdomain",
163 help="Set IMS Home Network Domain Name in FQDN format",
164 )
165 parser.add_option("--impi", dest="impi",
166 help="Set IMS private user identity",
167 )
168 parser.add_option("--impu", dest="impu",
169 help="Set IMS public user identity",
170 )
171 parser.add_option("--read-imsi", dest="read_imsi", action="store_true",
172 help="Read the IMSI from the CARD", default=False
173 )
174 parser.add_option("--read-iccid", dest="read_iccid", action="store_true",
175 help="Read the ICCID from the CARD", default=False
176 )
177 parser.add_option("-z", "--secret", dest="secret", metavar="STR",
178 help="Secret used for ICCID/IMSI autogen",
179 )
180 parser.add_option("-j", "--num", dest="num", type=int,
181 help="Card # used for ICCID/IMSI autogen",
182 )
183 parser.add_option("--batch", dest="batch_mode",
184 help="Enable batch mode [default: %default]",
185 default=False, action='store_true',
186 )
187 parser.add_option("--batch-state", dest="batch_state", metavar="FILE",
188 help="Optional batch state file",
189 )
Sylvain Munaut76504e02010-12-07 00:24:32 +0100190
Harald Weltec91085e2022-02-10 18:05:45 +0100191 # if mode is "csv"
192 parser.add_option("--read-csv", dest="read_csv", metavar="FILE",
193 help="Read parameters from CSV file rather than command line")
Harald Welte7f62cec2012-08-13 20:07:41 +0200194
Harald Weltec91085e2022-02-10 18:05:45 +0100195 parser.add_option("--write-csv", dest="write_csv", metavar="FILE",
196 help="Append generated parameters in CSV file",
197 )
198 parser.add_option("--write-hlr", dest="write_hlr", metavar="FILE",
199 help="Append generated parameters to OpenBSC HLR sqlite3",
200 )
201 parser.add_option("--dry-run", dest="dry_run",
202 help="Perform a 'dry run', don't actually program the card",
203 default=False, action="store_true")
204 parser.add_option("--card_handler", dest="card_handler_config", metavar="FILE",
205 help="Use automatic card handling machine")
Harald Welte7f62cec2012-08-13 20:07:41 +0200206
Harald Weltec91085e2022-02-10 18:05:45 +0100207 (options, args) = parser.parse_args()
Philipp Maierc5b422e2019-08-30 11:41:02 +0200208
Harald Weltec91085e2022-02-10 18:05:45 +0100209 if options.type == 'list':
210 for kls in _cards_classes:
211 print(kls.name)
212 sys.exit(0)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100213
Harald Weltec91085e2022-02-10 18:05:45 +0100214 if options.probe:
215 return options
Sylvain Munaut76504e02010-12-07 00:24:32 +0100216
Harald Weltec91085e2022-02-10 18:05:45 +0100217 if options.source == 'csv':
218 if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False):
219 parser.error(
220 "CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode")
221 if options.read_csv is None:
222 parser.error("CSV mode requires a CSV input file")
223 elif options.source == 'cmdline':
224 if ((options.imsi is None) or (options.iccid is None)) and (options.num is None):
225 parser.error(
226 "If either IMSI or ICCID isn't specified, num is required")
227 else:
228 parser.error("Only `cmdline' and `csv' sources supported")
Philipp Maierac9dde62018-07-04 11:05:14 +0200229
Harald Weltec91085e2022-02-10 18:05:45 +0100230 if (options.read_csv is not None) and (options.source != 'csv'):
231 parser.error("You cannot specify a CSV input file in source != csv")
Harald Welte7f62cec2012-08-13 20:07:41 +0200232
Harald Weltec91085e2022-02-10 18:05:45 +0100233 if (options.batch_mode) and (options.num is None):
234 options.num = 0
Harald Welte7f62cec2012-08-13 20:07:41 +0200235
Harald Weltec91085e2022-02-10 18:05:45 +0100236 if (options.batch_mode):
237 if (options.imsi is not None) or (options.iccid is not None):
238 parser.error(
239 "Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more information")
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100240
Harald Weltec91085e2022-02-10 18:05:45 +0100241 if args:
242 parser.error("Extraneous arguments")
Sylvain Munaut98d2b852010-12-23 20:27:25 +0100243
Harald Weltec91085e2022-02-10 18:05:45 +0100244 return options
Sylvain Munaut76504e02010-12-07 00:24:32 +0100245
246
247def _digits(secret, usage, len, num):
Harald Weltec91085e2022-02-10 18:05:45 +0100248 seed = secret + usage + '%d' % num
249 s = hashlib.sha1(seed.encode())
250 d = ''.join(['%02d' % x for x in s.digest()])
251 return d[0:len]
252
Sylvain Munaut76504e02010-12-07 00:24:32 +0100253
254def _mcc_mnc_digits(mcc, mnc):
Harald Weltec91085e2022-02-10 18:05:45 +0100255 return '%s%s' % (mcc, mnc)
256
Sylvain Munaut76504e02010-12-07 00:24:32 +0100257
258def _cc_digits(cc):
Harald Weltec91085e2022-02-10 18:05:45 +0100259 return ('%03d' if cc > 100 else '%02d') % cc
260
Sylvain Munaut76504e02010-12-07 00:24:32 +0100261
262def _isnum(s, l=-1):
Harald Weltec91085e2022-02-10 18:05:45 +0100263 return s.isdigit() and ((l == -1) or (len(s) == l))
264
Sylvain Munaut76504e02010-12-07 00:24:32 +0100265
Sylvain Munaut607ce2a2011-12-08 20:16:43 +0100266def _ishex(s, l=-1):
Harald Weltec91085e2022-02-10 18:05:45 +0100267 hc = '0123456789abcdef'
268 return all([x in hc for x in s.lower()]) and ((l == -1) or (len(s) == l))
Sylvain Munaut607ce2a2011-12-08 20:16:43 +0100269
Sylvain Munaut76504e02010-12-07 00:24:32 +0100270
Sylvain Munaut9f120e02010-12-23 20:28:24 +0100271def _dbi_binary_quote(s):
Harald Weltec91085e2022-02-10 18:05:45 +0100272 # Count usage of each char
273 cnt = {}
274 for c in s:
275 cnt[c] = cnt.get(c, 0) + 1
Sylvain Munaut9f120e02010-12-23 20:28:24 +0100276
Harald Weltec91085e2022-02-10 18:05:45 +0100277 # Find best offset
278 e = 0
279 m = len(s)
280 for i in range(1, 256):
281 if i == 39:
282 continue
283 sum_ = cnt.get(i, 0) + cnt.get((i+1) & 0xff, 0) + \
284 cnt.get((i+39) & 0xff, 0)
285 if sum_ < m:
286 m = sum_
287 e = i
288 if m == 0: # No overhead ? use this !
289 break
Sylvain Munaut1a914432011-12-08 20:08:26 +0100290
Harald Weltec91085e2022-02-10 18:05:45 +0100291 # Generate output
292 out = []
293 out.append(chr(e)) # Offset
294 for c in s:
295 x = (256 + ord(c) - e) % 256
296 if x in (0, 1, 39):
297 out.append('\x01')
298 out.append(chr(x+1))
299 else:
300 out.append(chr(x))
Sylvain Munaut9f120e02010-12-23 20:28:24 +0100301
Harald Weltec91085e2022-02-10 18:05:45 +0100302 return ''.join(out)
303
Sylvain Munaut9f120e02010-12-23 20:28:24 +0100304
Sylvain Munaut76504e02010-12-07 00:24:32 +0100305def gen_parameters(opts):
Harald Weltec91085e2022-02-10 18:05:45 +0100306 """Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the
307 options given by the user"""
Sylvain Munaut76504e02010-12-07 00:24:32 +0100308
Harald Weltec91085e2022-02-10 18:05:45 +0100309 # MCC/MNC
310 mcc = opts.mcc
311 mnc = opts.mnc
Sylvain Munaut76504e02010-12-07 00:24:32 +0100312
Harald Weltec91085e2022-02-10 18:05:45 +0100313 if not mcc.isdigit() or not mnc.isdigit():
314 raise ValueError('mcc & mnc must only contain decimal digits')
315 if len(mcc) < 1 or len(mcc) > 3:
316 raise ValueError('mcc must be between 1 .. 3 digits')
317 if len(mnc) < 1 or len(mnc) > 3:
318 raise ValueError('mnc must be between 1 .. 3 digits')
Harald Welte7f1d3c42020-05-12 21:12:44 +0200319
Harald Weltec91085e2022-02-10 18:05:45 +0100320 # MCC always has 3 digits
321 mcc = lpad(mcc, 3, "0")
Philipp Maier32c04342022-12-16 16:39:24 +0100322
323 # The MNC must be at least 2 digits long. This is also the most common case.
324 # The user may specify an explicit length using the --mnclen option.
325 if opts.mnclen != "auto":
326 if len(mnc) > int(opts.mnclen):
327 raise ValueError('mcc is longer than specified in option --mnclen')
328 mnc = lpad(mnc, int(opts.mnclen), "0")
329 else:
330 mnc = lpad(mnc, 2, "0")
Sylvain Munaut76504e02010-12-07 00:24:32 +0100331
Harald Weltec91085e2022-02-10 18:05:45 +0100332 # Digitize country code (2 or 3 digits)
333 cc_digits = _cc_digits(opts.country)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100334
Harald Weltec91085e2022-02-10 18:05:45 +0100335 # Digitize MCC/MNC (5 or 6 digits)
336 plmn_digits = _mcc_mnc_digits(mcc, mnc)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100337
Harald Weltec91085e2022-02-10 18:05:45 +0100338 if opts.name is not None:
339 if len(opts.name) > 16:
340 raise ValueError('Service Provider Name must max 16 characters!')
Supreeth Herle840a9e22020-01-21 13:32:46 +0100341
Harald Weltec91085e2022-02-10 18:05:45 +0100342 if opts.msisdn is not None:
343 msisdn = opts.msisdn
344 if msisdn[0] == '+':
345 msisdn = msisdn[1:]
346 if not msisdn.isdigit():
347 raise ValueError('MSISDN must be digits only! '
348 'Start with \'+\' for international numbers.')
349 if len(msisdn) > 10 * 2:
350 # TODO: Support MSISDN of length > 20 (10 Bytes)
351 raise ValueError(
352 'MSISDNs longer than 20 digits are not (yet) supported.')
Supreeth Herle5a541012019-12-22 08:59:16 +0100353
Harald Weltec91085e2022-02-10 18:05:45 +0100354 # ICCID (19 digits, E.118), though some phase1 vendors use 20 :(
355 if opts.iccid is not None:
356 iccid = opts.iccid
357 if not _isnum(iccid, 19) and not _isnum(iccid, 20):
358 raise ValueError('ICCID must be 19 or 20 digits !')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100359
Harald Weltec91085e2022-02-10 18:05:45 +0100360 else:
361 if opts.num is None:
362 raise ValueError('Neither ICCID nor card number specified !')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100363
Harald Weltec91085e2022-02-10 18:05:45 +0100364 iccid = (
365 '89' + # Common prefix (telecom)
366 cc_digits + # Country Code on 2/3 digits
367 plmn_digits # MCC/MNC on 5/6 digits
368 )
Sylvain Munaut76504e02010-12-07 00:24:32 +0100369
Harald Weltec91085e2022-02-10 18:05:45 +0100370 ml = 18 - len(iccid)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100371
Harald Weltec91085e2022-02-10 18:05:45 +0100372 if opts.secret is None:
373 # The raw number
374 iccid += ('%%0%dd' % ml) % opts.num
375 else:
376 # Randomized digits
377 iccid += _digits(opts.secret, 'ccid', ml, opts.num)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100378
Harald Weltec91085e2022-02-10 18:05:45 +0100379 # Add checksum digit
380 iccid += ('%1d' % calculate_luhn(iccid))
Harald Welte2c0ff3a2011-12-07 12:34:13 +0100381
Harald Weltec91085e2022-02-10 18:05:45 +0100382 # IMSI (15 digits usually)
383 if opts.imsi is not None:
384 imsi = opts.imsi
385 if not _isnum(imsi):
386 raise ValueError('IMSI must be digits only !')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100387
Harald Weltec91085e2022-02-10 18:05:45 +0100388 else:
389 if opts.num is None:
390 raise ValueError('Neither IMSI nor card number specified !')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100391
Harald Weltec91085e2022-02-10 18:05:45 +0100392 ml = 15 - len(plmn_digits)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100393
Harald Weltec91085e2022-02-10 18:05:45 +0100394 if opts.secret is None:
395 # The raw number
396 msin = ('%%0%dd' % ml) % opts.num
397 else:
398 # Randomized digits
399 msin = _digits(opts.secret, 'imsi', ml, opts.num)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100400
Harald Weltec91085e2022-02-10 18:05:45 +0100401 imsi = (
402 plmn_digits + # MCC/MNC on 5/6 digits
403 msin # MSIN
404 )
Sylvain Munaut76504e02010-12-07 00:24:32 +0100405
Harald Weltec91085e2022-02-10 18:05:45 +0100406 # SMSP
407 if opts.smsp is not None:
408 smsp = opts.smsp
409 if not _ishex(smsp):
410 raise ValueError('SMSP must be hex digits only !')
411 if len(smsp) < 28*2:
412 raise ValueError('SMSP must be at least 28 bytes')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100413
Harald Weltec91085e2022-02-10 18:05:45 +0100414 else:
415 ton = "81"
416 if opts.smsc is not None:
417 smsc = opts.smsc
418 if smsc[0] == '+':
419 ton = "91"
420 smsc = smsc[1:]
421 if not _isnum(smsc):
422 raise ValueError('SMSC must be digits only!\n \
Daniel Willmann4fa8f1c2018-10-02 18:10:21 +0200423 Start with \'+\' for international numbers')
Harald Weltec91085e2022-02-10 18:05:45 +0100424 else:
425 smsc = '00%d' % opts.country + '5555' # Hack ...
Sylvain Munaut607ce2a2011-12-08 20:16:43 +0100426
Harald Weltec91085e2022-02-10 18:05:45 +0100427 smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + \
428 swap_nibbles(rpad(smsc, 20))
Sylvain Munaut607ce2a2011-12-08 20:16:43 +0100429
Harald Weltec91085e2022-02-10 18:05:45 +0100430 smsp = (
431 'e1' + # Parameters indicator
432 'ff' * 12 + # TP-Destination address
433 smsc + # TP-Service Centre Address
434 '00' + # TP-Protocol identifier
435 '00' + # TP-Data coding scheme
436 '00' # TP-Validity period
437 )
Sylvain Munaut76504e02010-12-07 00:24:32 +0100438
Harald Weltec91085e2022-02-10 18:05:45 +0100439 # ACC
440 if opts.acc is not None:
441 acc = opts.acc
442 if not _ishex(acc):
443 raise ValueError('ACC must be hex digits only !')
444 if len(acc) != 2*2:
445 raise ValueError('ACC must be exactly 2 bytes')
Alexander Chemeris21885242013-07-02 16:56:55 +0400446
Harald Weltec91085e2022-02-10 18:05:45 +0100447 else:
448 acc = None
Alexander Chemeris21885242013-07-02 16:56:55 +0400449
Harald Weltec91085e2022-02-10 18:05:45 +0100450 # Ki (random)
451 if opts.ki is not None:
452 ki = opts.ki
453 if not re.match('^[0-9a-fA-F]{32}$', ki):
454 raise ValueError('Ki needs to be 128 bits, in hex format')
455 else:
456 ki = ''.join(['%02x' % random.randrange(0, 256) for i in range(16)])
Sylvain Munaut76504e02010-12-07 00:24:32 +0100457
Harald Weltec91085e2022-02-10 18:05:45 +0100458 # OPC (random)
459 if opts.opc is not None:
460 opc = opts.opc
461 if not re.match('^[0-9a-fA-F]{32}$', opc):
462 raise ValueError('OPC needs to be 128 bits, in hex format')
Harald Welte93b38cd2012-03-22 14:31:36 +0100463
Harald Weltec91085e2022-02-10 18:05:45 +0100464 elif opts.op is not None:
465 opc = derive_milenage_opc(ki, opts.op)
466 else:
467 opc = ''.join(['%02x' % random.randrange(0, 256) for i in range(16)])
Harald Welte93b38cd2012-03-22 14:31:36 +0100468
Harald Weltec91085e2022-02-10 18:05:45 +0100469 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
Harald Welte93b38cd2012-03-22 14:31:36 +0100470
Harald Weltec91085e2022-02-10 18:05:45 +0100471 # ePDG Selection Information
472 if opts.epdgSelection:
473 if len(opts.epdgSelection) < 5 or len(opts.epdgSelection) > 6:
474 raise ValueError('ePDG Selection Information is not valid')
475 epdg_mcc = opts.epdgSelection[:3]
476 epdg_mnc = opts.epdgSelection[3:]
477 if not epdg_mcc.isdigit() or not epdg_mnc.isdigit():
478 raise ValueError(
479 'PLMN for ePDG Selection must only contain decimal digits')
Supreeth Herlef964df42020-03-24 13:15:37 +0100480
Harald Weltec91085e2022-02-10 18:05:45 +0100481 # Return that
482 return {
483 'name': opts.name,
484 'iccid': iccid,
485 'mcc': mcc,
486 'mnc': mnc,
487 'imsi': imsi,
488 'smsp': smsp,
489 'ki': ki,
490 'opc': opc,
491 'acc': acc,
492 'pin_adm': pin_adm,
493 'msisdn': opts.msisdn,
494 'epdgid': opts.epdgid,
495 'epdgSelection': opts.epdgSelection,
496 'pcscf': opts.pcscf,
497 'ims_hdomain': opts.ims_hdomain,
498 'impi': opts.impi,
499 'impu': opts.impu,
500 'opmode': opts.opmode,
501 }
Sylvain Munaut76504e02010-12-07 00:24:32 +0100502
503
504def print_parameters(params):
505
Harald Weltec91085e2022-02-10 18:05:45 +0100506 s = ["Generated card parameters :"]
507 if 'name' in params:
508 s.append(" > Name : %(name)s")
509 if 'smsp' in params:
510 s.append(" > SMSP : %(smsp)s")
511 s.append(" > ICCID : %(iccid)s")
512 s.append(" > MCC/MNC : %(mcc)s/%(mnc)s")
513 s.append(" > IMSI : %(imsi)s")
514 s.append(" > Ki : %(ki)s")
515 s.append(" > OPC : %(opc)s")
516 if 'acc' in params:
517 s.append(" > ACC : %(acc)s")
518 s.append(" > ADM1(hex): %(pin_adm)s")
519 if 'opmode' in params:
520 s.append(" > OPMODE : %(opmode)s")
521 print("\n".join(s) % params)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100522
523
Harald Welte130524b2012-08-13 15:53:43 +0200524def write_params_csv(opts, params):
Harald Weltec91085e2022-02-10 18:05:45 +0100525 # csv
526 if opts.write_csv:
527 import csv
528 row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki', 'opc']
529 f = open(opts.write_csv, 'a')
530 cw = csv.writer(f)
531 cw.writerow([params[x] for x in row])
532 f.close()
533
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100534
Philipp Maier2688ddf2022-12-16 13:36:42 +0100535def find_row_in_csv_file(csv_file_name:str, num=None, iccid=None, imsi=None):
536 """
537 Pick a matching row in a CSV file by row number or ICCID or IMSI. When num
538 is not None, the search parameters iccid and imsi are ignored. When
539 searching for a specific ICCID or IMSI the caller must set num to None. It
540 is possible to search for an ICCID or an IMSI at the same time. The first
541 line that either contains a matching ICCID or IMSI is returned. Unused
542 search parameters must be set to None.
543 """
544
545 f = open(csv_file_name, 'r')
Harald Weltec91085e2022-02-10 18:05:45 +0100546 cr = csv.DictReader(f)
Philipp Maier120a0002019-09-12 13:11:45 +0200547
Philipp Maier2688ddf2022-12-16 13:36:42 +0100548 # Make sure the CSV file contains at least the fields we are searching for
549 if not 'iccid' in cr.fieldnames:
550 raise Exception("wrong CSV file format - no field \"iccid\" or missing header!")
551 if not 'imsi' in cr.fieldnames:
552 raise Exception("wrong CSV file format - no field \"imsi\" or missing header!")
553
554 # Enforce at least one search parameter
555 if not num and not iccid and not imsi:
556 raise Exception("no CSV file search parameters!")
557
Harald Weltec91085e2022-02-10 18:05:45 +0100558 # Lower-case fieldnames
559 cr.fieldnames = [field.lower() for field in cr.fieldnames]
Philipp Maier120a0002019-09-12 13:11:45 +0200560
Harald Weltec91085e2022-02-10 18:05:45 +0100561 i = 0
Harald Weltec91085e2022-02-10 18:05:45 +0100562 for row in cr:
Philipp Maier2688ddf2022-12-16 13:36:42 +0100563 # Pick a specific row by line number (num)
564 if num is not None and iccid is None and imsi is None:
Harald Weltec91085e2022-02-10 18:05:45 +0100565 if opts.num == i:
566 f.close()
567 return row
Philipp Maier2688ddf2022-12-16 13:36:42 +0100568
569 # Pick the first row that contains the specified ICCID
Harald Weltec91085e2022-02-10 18:05:45 +0100570 if row['iccid'] == iccid:
571 f.close()
572 return row
Daniel Willmann164b9632019-09-03 19:13:51 +0200573
Philipp Maier2688ddf2022-12-16 13:36:42 +0100574 # Pick the first row that contains the specified IMSI
Harald Weltec91085e2022-02-10 18:05:45 +0100575 if row['imsi'] == imsi:
576 f.close()
577 return row
Harald Welte7f62cec2012-08-13 20:07:41 +0200578
Philipp Maier2688ddf2022-12-16 13:36:42 +0100579 i += 1
580
Harald Weltec91085e2022-02-10 18:05:45 +0100581 f.close()
Philipp Maier2688ddf2022-12-16 13:36:42 +0100582 print("Could not read card parameters from CSV file, no matching entry found.")
Harald Weltec91085e2022-02-10 18:05:45 +0100583 return None
584
Harald Weltec26b8292012-08-15 15:25:51 +0200585
Daniel Willmann164b9632019-09-03 19:13:51 +0200586def read_params_csv(opts, imsi=None, iccid=None):
Philipp Maier4237ccf2022-12-20 11:21:47 +0100587 """
588 Read the card parameters from a CSV file. This function will generate the
589 same dictionary that gen_parameters would generate from parameters passed as
590 commandline arguments.
591 """
592
Philipp Maier2688ddf2022-12-16 13:36:42 +0100593 row = find_row_in_csv_file(opts.read_csv, opts.num, iccid=iccid, imsi=imsi)
Harald Weltec91085e2022-02-10 18:05:45 +0100594 if row is not None:
595 row['mcc'] = row.get('mcc', mcc_from_imsi(row.get('imsi')))
Philipp Maier32c04342022-12-16 16:39:24 +0100596
597 # We cannot determine the MNC length (2 or 3 digits?) from the IMSI
598 # alone. In cases where the user has specified an mnclen via the
599 # commandline options we can use that info, otherwise we guess that
600 # the length is 2, which is also the most common case.
601 if opts.mnclen != "auto":
Vadim Yanitskiy0d80fa92023-04-14 00:11:19 +0700602 if opts.mnclen == "2":
Philipp Maier32c04342022-12-16 16:39:24 +0100603 row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'), False))
Vadim Yanitskiy0d80fa92023-04-14 00:11:19 +0700604 elif opts.mnclen == "3":
Philipp Maier32c04342022-12-16 16:39:24 +0100605 row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'), True))
606 else:
607 raise ValueError("invalid parameter --mnclen, must be 2 or 3 or auto")
608 else:
609 row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'), False))
610
611 # NOTE: We might concider to specify a new CSV field "mnclen" in our
612 # CSV files for a better automatization. However, this only makes sense
613 # when the tools and databases we export our files from will also add
614 # such a field.
Philipp Maier7592eee2019-09-12 13:03:23 +0200615
Harald Weltec91085e2022-02-10 18:05:45 +0100616 pin_adm = None
617 # We need to escape the pin_adm we get from the csv
618 if 'pin_adm' in row:
619 pin_adm = ''.join(['%02x' % (ord(x)) for x in row['pin_adm']])
620 # Stay compatible to the odoo csv format
621 elif 'adm1' in row:
622 pin_adm = ''.join(['%02x' % (ord(x)) for x in row['adm1']])
623 if pin_adm:
624 row['pin_adm'] = rpad(pin_adm, 16)
Philipp Maiere053da52019-09-05 13:08:36 +0200625
Harald Weltec91085e2022-02-10 18:05:45 +0100626 # If the CSV-File defines a pin_adm_hex field use this field to
627 # generate pin_adm from that.
628 pin_adm_hex = row.get('pin_adm_hex')
629 if pin_adm_hex:
630 if len(pin_adm_hex) == 16:
631 row['pin_adm'] = pin_adm_hex
632 # Ensure that it's hex-encoded
633 try:
634 try_encode = h2b(pin_adm)
635 except ValueError:
636 raise ValueError(
637 "pin_adm_hex needs to be hex encoded using this option")
638 else:
639 raise ValueError(
640 "pin_adm_hex needs to be exactly 16 digits (hex encoded)")
Philipp Maiere053da52019-09-05 13:08:36 +0200641
Harald Weltec91085e2022-02-10 18:05:45 +0100642 return row
Harald Welte7f62cec2012-08-13 20:07:41 +0200643
Harald Weltec26b8292012-08-15 15:25:51 +0200644
Harald Welte130524b2012-08-13 15:53:43 +0200645def write_params_hlr(opts, params):
Harald Weltec91085e2022-02-10 18:05:45 +0100646 # SQLite3 OpenBSC HLR
647 if opts.write_hlr:
648 import sqlite3
649 conn = sqlite3.connect(opts.write_hlr)
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100650
Harald Weltec91085e2022-02-10 18:05:45 +0100651 c = conn.execute(
652 'INSERT INTO Subscriber ' +
653 '(imsi, name, extension, authorized, created, updated) ' +
654 'VALUES ' +
655 '(?,?,?,1,datetime(\'now\'),datetime(\'now\'));',
656 [
657 params['imsi'],
658 params['name'],
659 '9' + params['iccid'][-5:-1]
660 ],
661 )
662 sub_id = c.lastrowid
663 c.close()
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100664
Harald Weltec91085e2022-02-10 18:05:45 +0100665 c = conn.execute(
666 'INSERT INTO AuthKeys ' +
667 '(subscriber_id, algorithm_id, a3a8_ki)' +
668 'VALUES ' +
669 '(?,?,?)',
670 [sub_id, 2, sqlite3.Binary(
671 _dbi_binary_quote(h2b(params['ki'])))],
672 )
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100673
Harald Weltec91085e2022-02-10 18:05:45 +0100674 conn.commit()
675 conn.close()
676
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100677
Philipp Maier5f0cb3c2022-12-16 17:03:01 +0100678def write_parameters_to_csv_and_hlr(opts, params):
Harald Weltec91085e2022-02-10 18:05:45 +0100679 write_params_csv(opts, params)
680 write_params_hlr(opts, params)
Harald Welte130524b2012-08-13 15:53:43 +0200681
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100682
Harald Weltec91085e2022-02-10 18:05:45 +0100683BATCH_STATE = ['name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num']
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100684BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki']
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100685
Harald Weltec91085e2022-02-10 18:05:45 +0100686
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100687def init_batch(opts):
Harald Weltec91085e2022-02-10 18:05:45 +0100688 # Need to do something ?
689 if not opts.batch_mode:
690 return
Sylvain Munaut76504e02010-12-07 00:24:32 +0100691
Harald Weltec91085e2022-02-10 18:05:45 +0100692 for k in BATCH_INCOMPATIBLE:
693 if getattr(opts, k):
694 print("Incompatible option with batch_state: %s" % (k,))
695 sys.exit(-1)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100696
Harald Weltec91085e2022-02-10 18:05:45 +0100697 # Don't load state if there is none ...
698 if not opts.batch_state:
699 return
Sylvain Munaut76504e02010-12-07 00:24:32 +0100700
Harald Weltec91085e2022-02-10 18:05:45 +0100701 if not os.path.isfile(opts.batch_state):
702 print("No state file yet")
703 return
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100704
Harald Weltec91085e2022-02-10 18:05:45 +0100705 # Get stored data
706 fh = open(opts.batch_state)
707 d = json.loads(fh.read())
708 fh.close()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100709
Harald Weltec91085e2022-02-10 18:05:45 +0100710 for k, v in d.iteritems():
711 setattr(opts, k, v)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100712
713
714def save_batch(opts):
Harald Weltec91085e2022-02-10 18:05:45 +0100715 # Need to do something ?
716 if not opts.batch_mode or not opts.batch_state:
717 return
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100718
Harald Weltec91085e2022-02-10 18:05:45 +0100719 d = json.dumps(dict([(k, getattr(opts, k)) for k in BATCH_STATE]))
720 fh = open(opts.batch_state, 'w')
721 fh.write(d)
722 fh.close()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100723
724
Philipp Maier82511e52021-09-17 13:38:36 +0200725def process_card(opts, first, ch):
Philipp Maierc5b422e2019-08-30 11:41:02 +0200726
Philipp Maiercbb8c022022-12-16 16:57:16 +0100727 # Connect transport
728 ch.get(first)
729
730 # Get card
731 card = card_detect(opts.type, scc)
732 if card is None:
733 print("No card detected!")
734 return -1
735
736 # Probe only
737 if opts.probe:
738 return 0
739
740 # Erase if requested (not in dry run mode!)
Harald Weltec91085e2022-02-10 18:05:45 +0100741 if opts.dry_run is False:
Harald Weltec91085e2022-02-10 18:05:45 +0100742 if opts.erase:
743 print("Formatting ...")
744 card.erase()
745 card.reset()
Philipp Maierc5b422e2019-08-30 11:41:02 +0200746
Harald Weltec91085e2022-02-10 18:05:45 +0100747 # Generate parameters
748 if opts.source == 'cmdline':
749 cp = gen_parameters(opts)
750 elif opts.source == 'csv':
751 imsi = None
752 iccid = None
753 if opts.read_iccid:
Harald Weltec91085e2022-02-10 18:05:45 +0100754 (res, _) = scc.read_binary(['3f00', '2fe2'], length=10)
755 iccid = dec_iccid(res)
756 elif opts.read_imsi:
Harald Weltec91085e2022-02-10 18:05:45 +0100757 (res, _) = scc.read_binary(EF['IMSI'])
758 imsi = swap_nibbles(res)[3:]
759 else:
760 imsi = opts.imsi
761 cp = read_params_csv(opts, imsi=imsi, iccid=iccid)
762 if cp is None:
Harald Weltec91085e2022-02-10 18:05:45 +0100763 return 2
764 print_parameters(cp)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200765
Harald Weltec91085e2022-02-10 18:05:45 +0100766 if opts.dry_run is False:
767 # Program the card
768 print("Programming ...")
769 card.program(cp)
770 else:
771 print("Dry Run: NOT PROGRAMMING!")
Philipp Maierc5b422e2019-08-30 11:41:02 +0200772
Philipp Maier5f0cb3c2022-12-16 17:03:01 +0100773 # Write parameters to a specified CSV file or an HLR database (not the card)
774 write_parameters_to_csv_and_hlr(opts, cp)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200775
Harald Weltec91085e2022-02-10 18:05:45 +0100776 # Batch mode state update and save
777 if opts.num is not None:
778 opts.num += 1
779 save_batch(opts)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200780
Harald Weltec91085e2022-02-10 18:05:45 +0100781 ch.done()
782 return 0
Philipp Maierc5b422e2019-08-30 11:41:02 +0200783
784
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100785if __name__ == '__main__':
786
Harald Weltec91085e2022-02-10 18:05:45 +0100787 # Parse options
788 opts = parse_options()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100789
Harald Weltec91085e2022-02-10 18:05:45 +0100790 # Init card reader driver
791 sl = init_reader(opts)
792 if sl is None:
793 exit(1)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100794
Harald Weltec91085e2022-02-10 18:05:45 +0100795 # Create command layer
796 scc = SimCardCommands(transport=sl)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100797
Harald Weltec91085e2022-02-10 18:05:45 +0100798 # If we use a CSV file as data input, check if the CSV file exists.
799 if opts.source == 'csv':
800 print("Using CSV file as data input: " + str(opts.read_csv))
801 if not os.path.isfile(opts.read_csv):
802 print("CSV file not found!")
803 sys.exit(1)
Philipp Maier196b08c2019-09-12 11:49:44 +0200804
Harald Weltec91085e2022-02-10 18:05:45 +0100805 # Batch mode init
806 init_batch(opts)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100807
Harald Weltec91085e2022-02-10 18:05:45 +0100808 if opts.card_handler_config:
809 ch = CardHandlerAuto(sl, opts.card_handler_config)
810 else:
811 ch = CardHandler(sl)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200812
Harald Weltec91085e2022-02-10 18:05:45 +0100813 # Iterate
814 first = True
815 card = None
Sylvain Munaut1a914432011-12-08 20:08:26 +0100816
Harald Weltec91085e2022-02-10 18:05:45 +0100817 while 1:
818 try:
819 rc = process_card(opts, first, ch)
820 except (KeyboardInterrupt):
821 print("")
822 print("Terminated by user!")
823 sys.exit(0)
824 except (SystemExit):
825 raise
826 except:
827 print("")
828 print("Card programming failed with an exception:")
829 print("---------------------8<---------------------")
830 traceback.print_exc()
831 print("---------------------8<---------------------")
832 print("")
833 rc = -1
Harald Weltee9e5ecb2012-08-15 15:26:30 +0200834
Harald Weltec91085e2022-02-10 18:05:45 +0100835 # Something did not work as well as expected, however, lets
836 # make sure the card is pulled from the reader.
837 if rc != 0:
838 ch.error()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100839
Harald Weltec91085e2022-02-10 18:05:45 +0100840 # If we are not in batch mode we are done in any case, so lets
841 # exit here.
842 if not opts.batch_mode:
843 sys.exit(rc)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200844
Harald Weltec91085e2022-02-10 18:05:45 +0100845 first = False