blob: 2981ada6feaa938ae83855b60d114bd0894b3729 [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]",
117 default=2,
118 choices=[2, 3],
119 )
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")
322 # MNC must be at least 2 digits
323 mnc = lpad(mnc, 2, "0")
Sylvain Munaut76504e02010-12-07 00:24:32 +0100324
Harald Weltec91085e2022-02-10 18:05:45 +0100325 # Digitize country code (2 or 3 digits)
326 cc_digits = _cc_digits(opts.country)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100327
Harald Weltec91085e2022-02-10 18:05:45 +0100328 # Digitize MCC/MNC (5 or 6 digits)
329 plmn_digits = _mcc_mnc_digits(mcc, mnc)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100330
Harald Weltec91085e2022-02-10 18:05:45 +0100331 if opts.name is not None:
332 if len(opts.name) > 16:
333 raise ValueError('Service Provider Name must max 16 characters!')
Supreeth Herle840a9e22020-01-21 13:32:46 +0100334
Harald Weltec91085e2022-02-10 18:05:45 +0100335 if opts.msisdn is not None:
336 msisdn = opts.msisdn
337 if msisdn[0] == '+':
338 msisdn = msisdn[1:]
339 if not msisdn.isdigit():
340 raise ValueError('MSISDN must be digits only! '
341 'Start with \'+\' for international numbers.')
342 if len(msisdn) > 10 * 2:
343 # TODO: Support MSISDN of length > 20 (10 Bytes)
344 raise ValueError(
345 'MSISDNs longer than 20 digits are not (yet) supported.')
Supreeth Herle5a541012019-12-22 08:59:16 +0100346
Harald Weltec91085e2022-02-10 18:05:45 +0100347 # ICCID (19 digits, E.118), though some phase1 vendors use 20 :(
348 if opts.iccid is not None:
349 iccid = opts.iccid
350 if not _isnum(iccid, 19) and not _isnum(iccid, 20):
351 raise ValueError('ICCID must be 19 or 20 digits !')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100352
Harald Weltec91085e2022-02-10 18:05:45 +0100353 else:
354 if opts.num is None:
355 raise ValueError('Neither ICCID nor card number specified !')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100356
Harald Weltec91085e2022-02-10 18:05:45 +0100357 iccid = (
358 '89' + # Common prefix (telecom)
359 cc_digits + # Country Code on 2/3 digits
360 plmn_digits # MCC/MNC on 5/6 digits
361 )
Sylvain Munaut76504e02010-12-07 00:24:32 +0100362
Harald Weltec91085e2022-02-10 18:05:45 +0100363 ml = 18 - len(iccid)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100364
Harald Weltec91085e2022-02-10 18:05:45 +0100365 if opts.secret is None:
366 # The raw number
367 iccid += ('%%0%dd' % ml) % opts.num
368 else:
369 # Randomized digits
370 iccid += _digits(opts.secret, 'ccid', ml, opts.num)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100371
Harald Weltec91085e2022-02-10 18:05:45 +0100372 # Add checksum digit
373 iccid += ('%1d' % calculate_luhn(iccid))
Harald Welte2c0ff3a2011-12-07 12:34:13 +0100374
Harald Weltec91085e2022-02-10 18:05:45 +0100375 # IMSI (15 digits usually)
376 if opts.imsi is not None:
377 imsi = opts.imsi
378 if not _isnum(imsi):
379 raise ValueError('IMSI must be digits only !')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100380
Harald Weltec91085e2022-02-10 18:05:45 +0100381 else:
382 if opts.num is None:
383 raise ValueError('Neither IMSI nor card number specified !')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100384
Harald Weltec91085e2022-02-10 18:05:45 +0100385 ml = 15 - len(plmn_digits)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100386
Harald Weltec91085e2022-02-10 18:05:45 +0100387 if opts.secret is None:
388 # The raw number
389 msin = ('%%0%dd' % ml) % opts.num
390 else:
391 # Randomized digits
392 msin = _digits(opts.secret, 'imsi', ml, opts.num)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100393
Harald Weltec91085e2022-02-10 18:05:45 +0100394 imsi = (
395 plmn_digits + # MCC/MNC on 5/6 digits
396 msin # MSIN
397 )
Sylvain Munaut76504e02010-12-07 00:24:32 +0100398
Harald Weltec91085e2022-02-10 18:05:45 +0100399 # SMSP
400 if opts.smsp is not None:
401 smsp = opts.smsp
402 if not _ishex(smsp):
403 raise ValueError('SMSP must be hex digits only !')
404 if len(smsp) < 28*2:
405 raise ValueError('SMSP must be at least 28 bytes')
Sylvain Munaut76504e02010-12-07 00:24:32 +0100406
Harald Weltec91085e2022-02-10 18:05:45 +0100407 else:
408 ton = "81"
409 if opts.smsc is not None:
410 smsc = opts.smsc
411 if smsc[0] == '+':
412 ton = "91"
413 smsc = smsc[1:]
414 if not _isnum(smsc):
415 raise ValueError('SMSC must be digits only!\n \
Daniel Willmann4fa8f1c2018-10-02 18:10:21 +0200416 Start with \'+\' for international numbers')
Harald Weltec91085e2022-02-10 18:05:45 +0100417 else:
418 smsc = '00%d' % opts.country + '5555' # Hack ...
Sylvain Munaut607ce2a2011-12-08 20:16:43 +0100419
Harald Weltec91085e2022-02-10 18:05:45 +0100420 smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + \
421 swap_nibbles(rpad(smsc, 20))
Sylvain Munaut607ce2a2011-12-08 20:16:43 +0100422
Harald Weltec91085e2022-02-10 18:05:45 +0100423 smsp = (
424 'e1' + # Parameters indicator
425 'ff' * 12 + # TP-Destination address
426 smsc + # TP-Service Centre Address
427 '00' + # TP-Protocol identifier
428 '00' + # TP-Data coding scheme
429 '00' # TP-Validity period
430 )
Sylvain Munaut76504e02010-12-07 00:24:32 +0100431
Harald Weltec91085e2022-02-10 18:05:45 +0100432 # ACC
433 if opts.acc is not None:
434 acc = opts.acc
435 if not _ishex(acc):
436 raise ValueError('ACC must be hex digits only !')
437 if len(acc) != 2*2:
438 raise ValueError('ACC must be exactly 2 bytes')
Alexander Chemeris21885242013-07-02 16:56:55 +0400439
Harald Weltec91085e2022-02-10 18:05:45 +0100440 else:
441 acc = None
Alexander Chemeris21885242013-07-02 16:56:55 +0400442
Harald Weltec91085e2022-02-10 18:05:45 +0100443 # Ki (random)
444 if opts.ki is not None:
445 ki = opts.ki
446 if not re.match('^[0-9a-fA-F]{32}$', ki):
447 raise ValueError('Ki needs to be 128 bits, in hex format')
448 else:
449 ki = ''.join(['%02x' % random.randrange(0, 256) for i in range(16)])
Sylvain Munaut76504e02010-12-07 00:24:32 +0100450
Harald Weltec91085e2022-02-10 18:05:45 +0100451 # OPC (random)
452 if opts.opc is not None:
453 opc = opts.opc
454 if not re.match('^[0-9a-fA-F]{32}$', opc):
455 raise ValueError('OPC needs to be 128 bits, in hex format')
Harald Welte93b38cd2012-03-22 14:31:36 +0100456
Harald Weltec91085e2022-02-10 18:05:45 +0100457 elif opts.op is not None:
458 opc = derive_milenage_opc(ki, opts.op)
459 else:
460 opc = ''.join(['%02x' % random.randrange(0, 256) for i in range(16)])
Harald Welte93b38cd2012-03-22 14:31:36 +0100461
Harald Weltec91085e2022-02-10 18:05:45 +0100462 pin_adm = sanitize_pin_adm(opts.pin_adm, opts.pin_adm_hex)
Harald Welte93b38cd2012-03-22 14:31:36 +0100463
Harald Weltec91085e2022-02-10 18:05:45 +0100464 # ePDG Selection Information
465 if opts.epdgSelection:
466 if len(opts.epdgSelection) < 5 or len(opts.epdgSelection) > 6:
467 raise ValueError('ePDG Selection Information is not valid')
468 epdg_mcc = opts.epdgSelection[:3]
469 epdg_mnc = opts.epdgSelection[3:]
470 if not epdg_mcc.isdigit() or not epdg_mnc.isdigit():
471 raise ValueError(
472 'PLMN for ePDG Selection must only contain decimal digits')
Supreeth Herlef964df42020-03-24 13:15:37 +0100473
Harald Weltec91085e2022-02-10 18:05:45 +0100474 # Return that
475 return {
476 'name': opts.name,
477 'iccid': iccid,
478 'mcc': mcc,
479 'mnc': mnc,
480 'imsi': imsi,
481 'smsp': smsp,
482 'ki': ki,
483 'opc': opc,
484 'acc': acc,
485 'pin_adm': pin_adm,
486 'msisdn': opts.msisdn,
487 'epdgid': opts.epdgid,
488 'epdgSelection': opts.epdgSelection,
489 'pcscf': opts.pcscf,
490 'ims_hdomain': opts.ims_hdomain,
491 'impi': opts.impi,
492 'impu': opts.impu,
493 'opmode': opts.opmode,
494 }
Sylvain Munaut76504e02010-12-07 00:24:32 +0100495
496
497def print_parameters(params):
498
Harald Weltec91085e2022-02-10 18:05:45 +0100499 s = ["Generated card parameters :"]
500 if 'name' in params:
501 s.append(" > Name : %(name)s")
502 if 'smsp' in params:
503 s.append(" > SMSP : %(smsp)s")
504 s.append(" > ICCID : %(iccid)s")
505 s.append(" > MCC/MNC : %(mcc)s/%(mnc)s")
506 s.append(" > IMSI : %(imsi)s")
507 s.append(" > Ki : %(ki)s")
508 s.append(" > OPC : %(opc)s")
509 if 'acc' in params:
510 s.append(" > ACC : %(acc)s")
511 s.append(" > ADM1(hex): %(pin_adm)s")
512 if 'opmode' in params:
513 s.append(" > OPMODE : %(opmode)s")
514 print("\n".join(s) % params)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100515
516
Harald Welte130524b2012-08-13 15:53:43 +0200517def write_params_csv(opts, params):
Harald Weltec91085e2022-02-10 18:05:45 +0100518 # csv
519 if opts.write_csv:
520 import csv
521 row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki', 'opc']
522 f = open(opts.write_csv, 'a')
523 cw = csv.writer(f)
524 cw.writerow([params[x] for x in row])
525 f.close()
526
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100527
Philipp Maier2688ddf2022-12-16 13:36:42 +0100528def find_row_in_csv_file(csv_file_name:str, num=None, iccid=None, imsi=None):
529 """
530 Pick a matching row in a CSV file by row number or ICCID or IMSI. When num
531 is not None, the search parameters iccid and imsi are ignored. When
532 searching for a specific ICCID or IMSI the caller must set num to None. It
533 is possible to search for an ICCID or an IMSI at the same time. The first
534 line that either contains a matching ICCID or IMSI is returned. Unused
535 search parameters must be set to None.
536 """
537
538 f = open(csv_file_name, 'r')
Harald Weltec91085e2022-02-10 18:05:45 +0100539 cr = csv.DictReader(f)
Philipp Maier120a0002019-09-12 13:11:45 +0200540
Philipp Maier2688ddf2022-12-16 13:36:42 +0100541 # Make sure the CSV file contains at least the fields we are searching for
542 if not 'iccid' in cr.fieldnames:
543 raise Exception("wrong CSV file format - no field \"iccid\" or missing header!")
544 if not 'imsi' in cr.fieldnames:
545 raise Exception("wrong CSV file format - no field \"imsi\" or missing header!")
546
547 # Enforce at least one search parameter
548 if not num and not iccid and not imsi:
549 raise Exception("no CSV file search parameters!")
550
Harald Weltec91085e2022-02-10 18:05:45 +0100551 # Lower-case fieldnames
552 cr.fieldnames = [field.lower() for field in cr.fieldnames]
Philipp Maier120a0002019-09-12 13:11:45 +0200553
Harald Weltec91085e2022-02-10 18:05:45 +0100554 i = 0
Harald Weltec91085e2022-02-10 18:05:45 +0100555 for row in cr:
Philipp Maier2688ddf2022-12-16 13:36:42 +0100556 # Pick a specific row by line number (num)
557 if num is not None and iccid is None and imsi is None:
Harald Weltec91085e2022-02-10 18:05:45 +0100558 if opts.num == i:
559 f.close()
560 return row
Philipp Maier2688ddf2022-12-16 13:36:42 +0100561
562 # Pick the first row that contains the specified ICCID
Harald Weltec91085e2022-02-10 18:05:45 +0100563 if row['iccid'] == iccid:
564 f.close()
565 return row
Daniel Willmann164b9632019-09-03 19:13:51 +0200566
Philipp Maier2688ddf2022-12-16 13:36:42 +0100567 # Pick the first row that contains the specified IMSI
Harald Weltec91085e2022-02-10 18:05:45 +0100568 if row['imsi'] == imsi:
569 f.close()
570 return row
Harald Welte7f62cec2012-08-13 20:07:41 +0200571
Philipp Maier2688ddf2022-12-16 13:36:42 +0100572 i += 1
573
Harald Weltec91085e2022-02-10 18:05:45 +0100574 f.close()
Philipp Maier2688ddf2022-12-16 13:36:42 +0100575 print("Could not read card parameters from CSV file, no matching entry found.")
Harald Weltec91085e2022-02-10 18:05:45 +0100576 return None
577
Harald Weltec26b8292012-08-15 15:25:51 +0200578
Daniel Willmann164b9632019-09-03 19:13:51 +0200579def read_params_csv(opts, imsi=None, iccid=None):
Philipp Maier2688ddf2022-12-16 13:36:42 +0100580 row = find_row_in_csv_file(opts.read_csv, opts.num, iccid=iccid, imsi=imsi)
Harald Weltec91085e2022-02-10 18:05:45 +0100581 if row is not None:
582 row['mcc'] = row.get('mcc', mcc_from_imsi(row.get('imsi')))
583 row['mnc'] = row.get('mnc', mnc_from_imsi(row.get('imsi')))
Philipp Maier7592eee2019-09-12 13:03:23 +0200584
Harald Weltec91085e2022-02-10 18:05:45 +0100585 pin_adm = None
586 # We need to escape the pin_adm we get from the csv
587 if 'pin_adm' in row:
588 pin_adm = ''.join(['%02x' % (ord(x)) for x in row['pin_adm']])
589 # Stay compatible to the odoo csv format
590 elif 'adm1' in row:
591 pin_adm = ''.join(['%02x' % (ord(x)) for x in row['adm1']])
592 if pin_adm:
593 row['pin_adm'] = rpad(pin_adm, 16)
Philipp Maiere053da52019-09-05 13:08:36 +0200594
Harald Weltec91085e2022-02-10 18:05:45 +0100595 # If the CSV-File defines a pin_adm_hex field use this field to
596 # generate pin_adm from that.
597 pin_adm_hex = row.get('pin_adm_hex')
598 if pin_adm_hex:
599 if len(pin_adm_hex) == 16:
600 row['pin_adm'] = pin_adm_hex
601 # Ensure that it's hex-encoded
602 try:
603 try_encode = h2b(pin_adm)
604 except ValueError:
605 raise ValueError(
606 "pin_adm_hex needs to be hex encoded using this option")
607 else:
608 raise ValueError(
609 "pin_adm_hex needs to be exactly 16 digits (hex encoded)")
Philipp Maiere053da52019-09-05 13:08:36 +0200610
Harald Weltec91085e2022-02-10 18:05:45 +0100611 return row
Harald Welte7f62cec2012-08-13 20:07:41 +0200612
Harald Weltec26b8292012-08-15 15:25:51 +0200613
Harald Welte130524b2012-08-13 15:53:43 +0200614def write_params_hlr(opts, params):
Harald Weltec91085e2022-02-10 18:05:45 +0100615 # SQLite3 OpenBSC HLR
616 if opts.write_hlr:
617 import sqlite3
618 conn = sqlite3.connect(opts.write_hlr)
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100619
Harald Weltec91085e2022-02-10 18:05:45 +0100620 c = conn.execute(
621 'INSERT INTO Subscriber ' +
622 '(imsi, name, extension, authorized, created, updated) ' +
623 'VALUES ' +
624 '(?,?,?,1,datetime(\'now\'),datetime(\'now\'));',
625 [
626 params['imsi'],
627 params['name'],
628 '9' + params['iccid'][-5:-1]
629 ],
630 )
631 sub_id = c.lastrowid
632 c.close()
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100633
Harald Weltec91085e2022-02-10 18:05:45 +0100634 c = conn.execute(
635 'INSERT INTO AuthKeys ' +
636 '(subscriber_id, algorithm_id, a3a8_ki)' +
637 'VALUES ' +
638 '(?,?,?)',
639 [sub_id, 2, sqlite3.Binary(
640 _dbi_binary_quote(h2b(params['ki'])))],
641 )
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100642
Harald Weltec91085e2022-02-10 18:05:45 +0100643 conn.commit()
644 conn.close()
645
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100646
Harald Welte130524b2012-08-13 15:53:43 +0200647def write_parameters(opts, params):
Harald Weltec91085e2022-02-10 18:05:45 +0100648 write_params_csv(opts, params)
649 write_params_hlr(opts, params)
Harald Welte130524b2012-08-13 15:53:43 +0200650
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100651
Harald Weltec91085e2022-02-10 18:05:45 +0100652BATCH_STATE = ['name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num']
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100653BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki']
Sylvain Munaut143e99d2010-12-08 22:35:04 +0100654
Harald Weltec91085e2022-02-10 18:05:45 +0100655
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100656def init_batch(opts):
Harald Weltec91085e2022-02-10 18:05:45 +0100657 # Need to do something ?
658 if not opts.batch_mode:
659 return
Sylvain Munaut76504e02010-12-07 00:24:32 +0100660
Harald Weltec91085e2022-02-10 18:05:45 +0100661 for k in BATCH_INCOMPATIBLE:
662 if getattr(opts, k):
663 print("Incompatible option with batch_state: %s" % (k,))
664 sys.exit(-1)
Sylvain Munaut76504e02010-12-07 00:24:32 +0100665
Harald Weltec91085e2022-02-10 18:05:45 +0100666 # Don't load state if there is none ...
667 if not opts.batch_state:
668 return
Sylvain Munaut76504e02010-12-07 00:24:32 +0100669
Harald Weltec91085e2022-02-10 18:05:45 +0100670 if not os.path.isfile(opts.batch_state):
671 print("No state file yet")
672 return
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100673
Harald Weltec91085e2022-02-10 18:05:45 +0100674 # Get stored data
675 fh = open(opts.batch_state)
676 d = json.loads(fh.read())
677 fh.close()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100678
Harald Weltec91085e2022-02-10 18:05:45 +0100679 for k, v in d.iteritems():
680 setattr(opts, k, v)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100681
682
683def save_batch(opts):
Harald Weltec91085e2022-02-10 18:05:45 +0100684 # Need to do something ?
685 if not opts.batch_mode or not opts.batch_state:
686 return
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100687
Harald Weltec91085e2022-02-10 18:05:45 +0100688 d = json.dumps(dict([(k, getattr(opts, k)) for k in BATCH_STATE]))
689 fh = open(opts.batch_state, 'w')
690 fh.write(d)
691 fh.close()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100692
693
Philipp Maier82511e52021-09-17 13:38:36 +0200694def process_card(opts, first, ch):
Philipp Maierc5b422e2019-08-30 11:41:02 +0200695
Harald Weltec91085e2022-02-10 18:05:45 +0100696 if opts.dry_run is False:
697 # Connect transport
698 ch.get(first)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200699
Harald Weltec91085e2022-02-10 18:05:45 +0100700 if opts.dry_run is False:
701 # Get card
702 card = card_detect(opts.type, scc)
703 if card is None:
704 print("No card detected!")
705 return -1
Philipp Maierc5b422e2019-08-30 11:41:02 +0200706
Harald Weltec91085e2022-02-10 18:05:45 +0100707 # Probe only
708 if opts.probe:
709 return 0
Philipp Maierc5b422e2019-08-30 11:41:02 +0200710
Harald Weltec91085e2022-02-10 18:05:45 +0100711 # Erase if requested
712 if opts.erase:
713 print("Formatting ...")
714 card.erase()
715 card.reset()
Philipp Maierc5b422e2019-08-30 11:41:02 +0200716
Harald Weltec91085e2022-02-10 18:05:45 +0100717 # Generate parameters
718 if opts.source == 'cmdline':
719 cp = gen_parameters(opts)
720 elif opts.source == 'csv':
721 imsi = None
722 iccid = None
723 if opts.read_iccid:
724 if opts.dry_run:
725 # Connect transport
726 ch.get(False)
727 (res, _) = scc.read_binary(['3f00', '2fe2'], length=10)
728 iccid = dec_iccid(res)
729 elif opts.read_imsi:
730 if opts.dry_run:
731 # Connect transport
732 ch.get(False)
733 (res, _) = scc.read_binary(EF['IMSI'])
734 imsi = swap_nibbles(res)[3:]
735 else:
736 imsi = opts.imsi
737 cp = read_params_csv(opts, imsi=imsi, iccid=iccid)
738 if cp is None:
Harald Weltec91085e2022-02-10 18:05:45 +0100739 return 2
740 print_parameters(cp)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200741
Harald Weltec91085e2022-02-10 18:05:45 +0100742 if opts.dry_run is False:
743 # Program the card
744 print("Programming ...")
745 card.program(cp)
746 else:
747 print("Dry Run: NOT PROGRAMMING!")
Philipp Maierc5b422e2019-08-30 11:41:02 +0200748
Harald Weltec91085e2022-02-10 18:05:45 +0100749 # Write parameters permanently
750 write_parameters(opts, cp)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200751
Harald Weltec91085e2022-02-10 18:05:45 +0100752 # Batch mode state update and save
753 if opts.num is not None:
754 opts.num += 1
755 save_batch(opts)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200756
Harald Weltec91085e2022-02-10 18:05:45 +0100757 ch.done()
758 return 0
Philipp Maierc5b422e2019-08-30 11:41:02 +0200759
760
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100761if __name__ == '__main__':
762
Harald Weltec91085e2022-02-10 18:05:45 +0100763 # Parse options
764 opts = parse_options()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100765
Harald Weltec91085e2022-02-10 18:05:45 +0100766 # Init card reader driver
767 sl = init_reader(opts)
768 if sl is None:
769 exit(1)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100770
Harald Weltec91085e2022-02-10 18:05:45 +0100771 # Create command layer
772 scc = SimCardCommands(transport=sl)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100773
Harald Weltec91085e2022-02-10 18:05:45 +0100774 # If we use a CSV file as data input, check if the CSV file exists.
775 if opts.source == 'csv':
776 print("Using CSV file as data input: " + str(opts.read_csv))
777 if not os.path.isfile(opts.read_csv):
778 print("CSV file not found!")
779 sys.exit(1)
Philipp Maier196b08c2019-09-12 11:49:44 +0200780
Harald Weltec91085e2022-02-10 18:05:45 +0100781 # Batch mode init
782 init_batch(opts)
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100783
Harald Weltec91085e2022-02-10 18:05:45 +0100784 if opts.card_handler_config:
785 ch = CardHandlerAuto(sl, opts.card_handler_config)
786 else:
787 ch = CardHandler(sl)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200788
Harald Weltec91085e2022-02-10 18:05:45 +0100789 # Iterate
790 first = True
791 card = None
Sylvain Munaut1a914432011-12-08 20:08:26 +0100792
Harald Weltec91085e2022-02-10 18:05:45 +0100793 while 1:
794 try:
795 rc = process_card(opts, first, ch)
796 except (KeyboardInterrupt):
797 print("")
798 print("Terminated by user!")
799 sys.exit(0)
800 except (SystemExit):
801 raise
802 except:
803 print("")
804 print("Card programming failed with an exception:")
805 print("---------------------8<---------------------")
806 traceback.print_exc()
807 print("---------------------8<---------------------")
808 print("")
809 rc = -1
Harald Weltee9e5ecb2012-08-15 15:26:30 +0200810
Harald Weltec91085e2022-02-10 18:05:45 +0100811 # Something did not work as well as expected, however, lets
812 # make sure the card is pulled from the reader.
813 if rc != 0:
814 ch.error()
Sylvain Munaut8f7d3ba2010-12-09 13:32:48 +0100815
Harald Weltec91085e2022-02-10 18:05:45 +0100816 # If we are not in batch mode we are done in any case, so lets
817 # exit here.
818 if not opts.batch_mode:
819 sys.exit(rc)
Philipp Maierc5b422e2019-08-30 11:41:02 +0200820
Harald Weltec91085e2022-02-10 18:05:45 +0100821 first = False