blob: dbf1148e480a2f305aedc0630f3cfeca42b1ed55 [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 Maier2688ddf2022-12-16 13:36:42 +0100587 row = find_row_in_csv_file(opts.read_csv, opts.num, iccid=iccid, imsi=imsi)
Harald Weltec91085e2022-02-10 18:05:45 +0100588 if row is not None:
589 row['mcc'] = row.get('mcc', mcc_from_imsi(row.get('imsi')))
Philipp Maier32c04342022-12-16 16:39:24 +0100590
591 # We cannot determine the MNC length (2 or 3 digits?) from the IMSI
592 # alone. In cases where the user has specified an mnclen via the
593 # commandline options we can use that info, otherwise we guess that
594 # the length is 2, which is also the most common case.
595 if opts.mnclen != "auto":
596 if opts.mnclen is "2":
597 row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'), False))
598 elif opts.mnclen is "3":
599 row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'), True))
600 else:
601 raise ValueError("invalid parameter --mnclen, must be 2 or 3 or auto")
602 else:
603 row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi'), False))
604
605 # NOTE: We might concider to specify a new CSV field "mnclen" in our
606 # CSV files for a better automatization. However, this only makes sense
607 # when the tools and databases we export our files from will also add
608 # such a field.
Philipp Maier7592eee2019-09-12 13:03:23 +0200609
Harald Weltec91085e2022-02-10 18:05:45 +0100610 pin_adm = None
611 # We need to escape the pin_adm we get from the csv
612 if 'pin_adm' in row:
613 pin_adm = ''.join(['%02x' % (ord(x)) for x in row['pin_adm']])
614 # Stay compatible to the odoo csv format
615 elif 'adm1' in row:
616 pin_adm = ''.join(['%02x' % (ord(x)) for x in row['adm1']])
617 if pin_adm:
618 row['pin_adm'] = rpad(pin_adm, 16)
Philipp Maiere053da52019-09-05 13:08:36 +0200619
Harald Weltec91085e2022-02-10 18:05:45 +0100620 # If the CSV-File defines a pin_adm_hex field use this field to
621 # generate pin_adm from that.
622 pin_adm_hex = row.get('pin_adm_hex')
623 if pin_adm_hex:
624 if len(pin_adm_hex) == 16:
625 row['pin_adm'] = pin_adm_hex
626 # Ensure that it's hex-encoded
627 try:
628 try_encode = h2b(pin_adm)
629 except ValueError:
630 raise ValueError(
631 "pin_adm_hex needs to be hex encoded using this option")
632 else:
633 raise ValueError(
634 "pin_adm_hex needs to be exactly 16 digits (hex encoded)")
Philipp Maiere053da52019-09-05 13:08:36 +0200635
Harald Weltec91085e2022-02-10 18:05:45 +0100636 return row
Harald Welte7f62cec2012-08-13 20:07:41 +0200637
Harald Weltec26b8292012-08-15 15:25:51 +0200638
Harald Welte130524b2012-08-13 15:53:43 +0200639def write_params_hlr(opts, params):
Harald Weltec91085e2022-02-10 18:05:45 +0100640 # SQLite3 OpenBSC HLR
641 if opts.write_hlr:
642 import sqlite3
643 conn = sqlite3.connect(opts.write_hlr)
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100644
Harald Weltec91085e2022-02-10 18:05:45 +0100645 c = conn.execute(
646 'INSERT INTO Subscriber ' +
647 '(imsi, name, extension, authorized, created, updated) ' +
648 'VALUES ' +
649 '(?,?,?,1,datetime(\'now\'),datetime(\'now\'));',
650 [
651 params['imsi'],
652 params['name'],
653 '9' + params['iccid'][-5:-1]
654 ],
655 )
656 sub_id = c.lastrowid
657 c.close()
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100658
Harald Weltec91085e2022-02-10 18:05:45 +0100659 c = conn.execute(
660 'INSERT INTO AuthKeys ' +
661 '(subscriber_id, algorithm_id, a3a8_ki)' +
662 'VALUES ' +
663 '(?,?,?)',
664 [sub_id, 2, sqlite3.Binary(
665 _dbi_binary_quote(h2b(params['ki'])))],
666 )
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100667
Harald Weltec91085e2022-02-10 18:05:45 +0100668 conn.commit()
669 conn.close()
670
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100671
Harald Welte130524b2012-08-13 15:53:43 +0200672def write_parameters(opts, params):
Harald Weltec91085e2022-02-10 18:05:45 +0100673 write_params_csv(opts, params)
674 write_params_hlr(opts, params)
Harald Welte130524b2012-08-13 15:53:43 +0200675
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100676
Harald Weltec91085e2022-02-10 18:05:45 +0100677BATCH_STATE = ['name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num']
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100678BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki']
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100679
Harald Weltec91085e2022-02-10 18:05:45 +0100680
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100681def init_batch(opts):
Harald Weltec91085e2022-02-10 18:05:45 +0100682 # Need to do something ?
683 if not opts.batch_mode:
684 return
Sylvain Munaut76504e02010-12-07 00:24:32 +0100685
Harald Weltec91085e2022-02-10 18:05:45 +0100686 for k in BATCH_INCOMPATIBLE:
687 if getattr(opts, k):
688 print("Incompatible option with batch_state: %s" % (k,))
689 sys.exit(-1)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100690
Harald Weltec91085e2022-02-10 18:05:45 +0100691 # Don't load state if there is none ...
692 if not opts.batch_state:
693 return
Sylvain Munaut76504e02010-12-07 00:24:32 +0100694
Harald Weltec91085e2022-02-10 18:05:45 +0100695 if not os.path.isfile(opts.batch_state):
696 print("No state file yet")
697 return
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100698
Harald Weltec91085e2022-02-10 18:05:45 +0100699 # Get stored data
700 fh = open(opts.batch_state)
701 d = json.loads(fh.read())
702 fh.close()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100703
Harald Weltec91085e2022-02-10 18:05:45 +0100704 for k, v in d.iteritems():
705 setattr(opts, k, v)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100706
707
708def save_batch(opts):
Harald Weltec91085e2022-02-10 18:05:45 +0100709 # Need to do something ?
710 if not opts.batch_mode or not opts.batch_state:
711 return
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100712
Harald Weltec91085e2022-02-10 18:05:45 +0100713 d = json.dumps(dict([(k, getattr(opts, k)) for k in BATCH_STATE]))
714 fh = open(opts.batch_state, 'w')
715 fh.write(d)
716 fh.close()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100717
718
Philipp Maier82511e52021-09-17 13:38:36 +0200719def process_card(opts, first, ch):
Philipp Maierc5b422e2019-08-30 11:41:02 +0200720
Philipp Maiercbb8c022022-12-16 16:57:16 +0100721 # Connect transport
722 ch.get(first)
723
724 # Get card
725 card = card_detect(opts.type, scc)
726 if card is None:
727 print("No card detected!")
728 return -1
729
730 # Probe only
731 if opts.probe:
732 return 0
733
734 # Erase if requested (not in dry run mode!)
Harald Weltec91085e2022-02-10 18:05:45 +0100735 if opts.dry_run is False:
Harald Weltec91085e2022-02-10 18:05:45 +0100736 if opts.erase:
737 print("Formatting ...")
738 card.erase()
739 card.reset()
Philipp Maierc5b422e2019-08-30 11:41:02 +0200740
Harald Weltec91085e2022-02-10 18:05:45 +0100741 # Generate parameters
742 if opts.source == 'cmdline':
743 cp = gen_parameters(opts)
744 elif opts.source == 'csv':
745 imsi = None
746 iccid = None
747 if opts.read_iccid:
Harald Weltec91085e2022-02-10 18:05:45 +0100748 (res, _) = scc.read_binary(['3f00', '2fe2'], length=10)
749 iccid = dec_iccid(res)
750 elif opts.read_imsi:
Harald Weltec91085e2022-02-10 18:05:45 +0100751 (res, _) = scc.read_binary(EF['IMSI'])
752 imsi = swap_nibbles(res)[3:]
753 else:
754 imsi = opts.imsi
755 cp = read_params_csv(opts, imsi=imsi, iccid=iccid)
756 if cp is None:
Harald Weltec91085e2022-02-10 18:05:45 +0100757 return 2
758 print_parameters(cp)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200759
Harald Weltec91085e2022-02-10 18:05:45 +0100760 if opts.dry_run is False:
761 # Program the card
762 print("Programming ...")
763 card.program(cp)
764 else:
765 print("Dry Run: NOT PROGRAMMING!")
Philipp Maierc5b422e2019-08-30 11:41:02 +0200766
Harald Weltec91085e2022-02-10 18:05:45 +0100767 # Write parameters permanently
768 write_parameters(opts, cp)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200769
Harald Weltec91085e2022-02-10 18:05:45 +0100770 # Batch mode state update and save
771 if opts.num is not None:
772 opts.num += 1
773 save_batch(opts)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200774
Harald Weltec91085e2022-02-10 18:05:45 +0100775 ch.done()
776 return 0
Philipp Maierc5b422e2019-08-30 11:41:02 +0200777
778
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100779if __name__ == '__main__':
780
Harald Weltec91085e2022-02-10 18:05:45 +0100781 # Parse options
782 opts = parse_options()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100783
Harald Weltec91085e2022-02-10 18:05:45 +0100784 # Init card reader driver
785 sl = init_reader(opts)
786 if sl is None:
787 exit(1)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100788
Harald Weltec91085e2022-02-10 18:05:45 +0100789 # Create command layer
790 scc = SimCardCommands(transport=sl)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100791
Harald Weltec91085e2022-02-10 18:05:45 +0100792 # If we use a CSV file as data input, check if the CSV file exists.
793 if opts.source == 'csv':
794 print("Using CSV file as data input: " + str(opts.read_csv))
795 if not os.path.isfile(opts.read_csv):
796 print("CSV file not found!")
797 sys.exit(1)
Philipp Maier196b08c2019-09-12 11:49:44 +0200798
Harald Weltec91085e2022-02-10 18:05:45 +0100799 # Batch mode init
800 init_batch(opts)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100801
Harald Weltec91085e2022-02-10 18:05:45 +0100802 if opts.card_handler_config:
803 ch = CardHandlerAuto(sl, opts.card_handler_config)
804 else:
805 ch = CardHandler(sl)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200806
Harald Weltec91085e2022-02-10 18:05:45 +0100807 # Iterate
808 first = True
809 card = None
Sylvain Munaut1a914432011-12-08 20:08:26 +0100810
Harald Weltec91085e2022-02-10 18:05:45 +0100811 while 1:
812 try:
813 rc = process_card(opts, first, ch)
814 except (KeyboardInterrupt):
815 print("")
816 print("Terminated by user!")
817 sys.exit(0)
818 except (SystemExit):
819 raise
820 except:
821 print("")
822 print("Card programming failed with an exception:")
823 print("---------------------8<---------------------")
824 traceback.print_exc()
825 print("---------------------8<---------------------")
826 print("")
827 rc = -1
Harald Weltee9e5ecb2012-08-15 15:26:30 +0200828
Harald Weltec91085e2022-02-10 18:05:45 +0100829 # Something did not work as well as expected, however, lets
830 # make sure the card is pulled from the reader.
831 if rc != 0:
832 ch.error()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100833
Harald Weltec91085e2022-02-10 18:05:45 +0100834 # If we are not in batch mode we are done in any case, so lets
835 # exit here.
836 if not opts.batch_mode:
837 sys.exit(rc)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200838
Harald Weltec91085e2022-02-10 18:05:45 +0100839 first = False