Compare commits
15 Commits
users/dani
...
fairwaves/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dfda9fdd7 | ||
|
|
99b5e321e5 | ||
|
|
d21ef12f8d | ||
|
|
1198ad9e15 | ||
|
|
cc85a1ee71 | ||
|
|
2322c1f9ff | ||
|
|
6a38c4a2f3 | ||
|
|
575f64e38a | ||
|
|
4d5c0293a2 | ||
|
|
3a27424ff8 | ||
|
|
5705837a1b | ||
|
|
b5208b5544 | ||
|
|
4dabfda193 | ||
|
|
6d4a0a1a3e | ||
|
|
e6d4faa6f5 |
@@ -1,3 +0,0 @@
|
||||
[gerrit]
|
||||
host=gerrit.osmocom.org
|
||||
project=pysim
|
||||
16
README.md
16
README.md
@@ -28,22 +28,6 @@ You can clone from the official libosmocore.git repository using
|
||||
|
||||
There is a cgit interface at <http://git.osmocom.org/pysim/>
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
pysim requires:
|
||||
|
||||
- pyscard
|
||||
- serial
|
||||
- pytlv (for specific card types)
|
||||
|
||||
Example for Debian:
|
||||
|
||||
apt-get install python-pyscard python-serial python-pip
|
||||
pip install pytlv
|
||||
|
||||
|
||||
Mailing List
|
||||
------------
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ ! -d "./pysim-testdata/" ] ; then
|
||||
echo "###############################################"
|
||||
echo "Please call from pySim-prog top directory"
|
||||
echo "###############################################"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
virtualenv -p python2 venv --system-site-packages
|
||||
. venv/bin/activate
|
||||
pip install pytlv
|
||||
|
||||
cd pysim-testdata
|
||||
../tests/pysim-test.sh
|
||||
|
||||
123
fairwaves_db_randomize.py
Executable file
123
fairwaves_db_randomize.py
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to randomize Ki and other values in a Fairwaves SIM card DB file
|
||||
#
|
||||
# Copyright (C) 2017-2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
import random
|
||||
|
||||
from pySim.utils import derive_milenage_opc
|
||||
|
||||
#from pySim.utils import h2b
|
||||
def h2b(s):
|
||||
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])])
|
||||
|
||||
def load_sim_db(filename):
|
||||
sim_db = {}
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.reader(f, delimiter=' ')
|
||||
# Skip the header
|
||||
reader.next()
|
||||
for l in reader:
|
||||
sim_db[l[0]] = l
|
||||
return sim_db
|
||||
|
||||
def write_sim_db(filename, sim_db):
|
||||
with open(filename, 'a') as f:
|
||||
cw = csv.writer(f, delimiter=' ')
|
||||
for iccid in sorted(sim_db.iterkeys()):
|
||||
cw.writerow([x for x in sim_db[iccid]])
|
||||
|
||||
def process_sim(sim_keys, opts):
|
||||
# Update IMSI
|
||||
imsi = sim_keys[1]
|
||||
imsi = "%03d%02d%s" % (opts.mcc, opts.mnc, imsi[5:])
|
||||
sim_keys[1] = imsi
|
||||
|
||||
# Update Ki
|
||||
ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]).upper()
|
||||
sim_keys[8] = ki
|
||||
|
||||
# Update OPC
|
||||
op_opc = derive_milenage_opc(ki, opts.op).upper()
|
||||
sim_keys[9] = '01' + op_opc
|
||||
|
||||
return sim_keys
|
||||
|
||||
|
||||
def process_db(sim_db, opts):
|
||||
sim_db_new = {}
|
||||
for iccid, sim_keys in sim_db.items():
|
||||
sim_db_new[iccid] = process_sim(sim_keys, opts)
|
||||
return sim_db_new
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="Utility to randomize Ki and other values in a Fairwaves SIM card DB file.")
|
||||
|
||||
parser.add_option("-s", "--sim-db", dest="sim_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to load keys from (space separated)",
|
||||
default="sim_db.dat",
|
||||
)
|
||||
parser.add_option("-o", "--out-db", dest="out_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to write keys to (space separated)",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("-x", "--mcc", dest="mcc", type="int",
|
||||
help="Mobile Country Code [default: %default]",
|
||||
default=001,
|
||||
)
|
||||
parser.add_option("-y", "--mnc", dest="mnc", type="int",
|
||||
help="Mobile Network Code [default: %default]",
|
||||
default=01,
|
||||
)
|
||||
parser.add_option("--op", dest="op",
|
||||
help="Set OP to derive OPC from OP and KI [default: %default]",
|
||||
default='00000000000000000000000000000000',
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
if opts.out_db_filename is None:
|
||||
print("Please specify output DB filename")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loading SIM DB ...")
|
||||
sim_db = load_sim_db(opts.sim_db_filename)
|
||||
|
||||
sim_db = process_db(sim_db, opts)
|
||||
|
||||
print("Writing SIM DB ...")
|
||||
write_sim_db(opts.out_db_filename, sim_db)
|
||||
154
fairwaves_db_to_hlr.py
Executable file
154
fairwaves_db_to_hlr.py
Executable file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to write data from a Fairwaves SIM card DB to Osmocom HLR DB
|
||||
#
|
||||
# Copyright (C) 2017-2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
|
||||
#from pySim.utils import h2b
|
||||
def h2b(s):
|
||||
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])])
|
||||
|
||||
def load_sim_db(filename):
|
||||
sim_db = {}
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.reader(f, delimiter=' ')
|
||||
# Skip the header
|
||||
# reader.next()
|
||||
for l in reader:
|
||||
sim_db[l[0]] = l
|
||||
return sim_db
|
||||
|
||||
def _dbi_binary_quote(s):
|
||||
# Count usage of each char
|
||||
cnt = {}
|
||||
for c in s:
|
||||
cnt[c] = cnt.get(c, 0) + 1
|
||||
|
||||
# Find best offset
|
||||
e = 0
|
||||
m = len(s)
|
||||
for i in range(1, 256):
|
||||
if i == 39:
|
||||
continue
|
||||
sum_ = cnt.get(i, 0) + cnt.get((i+1)&0xff, 0) + cnt.get((i+39)&0xff, 0)
|
||||
if sum_ < m:
|
||||
m = sum_
|
||||
e = i
|
||||
if m == 0: # No overhead ? use this !
|
||||
break;
|
||||
|
||||
# Generate output
|
||||
out = []
|
||||
out.append( chr(e) ) # Offset
|
||||
for c in s:
|
||||
x = (256 + ord(c) - e) % 256
|
||||
if x in (0, 1, 39):
|
||||
out.append('\x01')
|
||||
out.append(chr(x+1))
|
||||
else:
|
||||
out.append(chr(x))
|
||||
|
||||
return ''.join(out)
|
||||
|
||||
def write_key_hlr(opts, sim_data):
|
||||
# SQLite3 OpenBSC HLR
|
||||
import sqlite3
|
||||
conn = sqlite3.connect(opts.hlr_db_filename)
|
||||
|
||||
imsi = sim_data[1]
|
||||
ki = sim_data[8]
|
||||
|
||||
c = conn.execute('SELECT id FROM Subscriber WHERE imsi = ?', (imsi,))
|
||||
sub_id = c.fetchone()
|
||||
if sub_id is None:
|
||||
print("IMSI %s is not found in the HLR" % (imsi,))
|
||||
return None
|
||||
sub_id = sub_id[0]
|
||||
print("IMSI %s has ID %d, writing Ki %s" % (imsi, sub_id, ki))
|
||||
|
||||
# c = conn.execute(
|
||||
# 'INSERT INTO Subscriber ' +
|
||||
# '(imsi, name, extension, authorized, created, updated) ' +
|
||||
# 'VALUES ' +
|
||||
# '(?,?,?,1,datetime(\'now\'),datetime(\'now\'));',
|
||||
# [
|
||||
# params['imsi'],
|
||||
# params['name'],
|
||||
# '9' + params['iccid'][-5:-1]
|
||||
# ],
|
||||
# )
|
||||
# sub_id = c.lastrowid
|
||||
# c.close()
|
||||
|
||||
c = conn.execute(
|
||||
'INSERT OR REPLACE INTO AuthKeys ' +
|
||||
'(subscriber_id, algorithm_id, a3a8_ki)' +
|
||||
'VALUES ' +
|
||||
'(?,?,?)',
|
||||
[ sub_id, 2, sqlite3.Binary(_dbi_binary_quote(h2b(ki))) ],
|
||||
)
|
||||
|
||||
c = conn.execute(
|
||||
'DELETE FROM AuthLastTuples WHERE subscriber_id = ?',
|
||||
[ sub_id ],
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return True
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="Utility to write data from a Fairwaves SIM card DB to Osmocom HLR DB.")
|
||||
|
||||
parser.add_option("-s", "--sim-db", dest="sim_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to load keys from (space searated)",
|
||||
default="sim_db.dat",
|
||||
)
|
||||
parser.add_option("-d", "--hlr", dest="hlr_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a HLR SQLite3 DB to write the keys to",
|
||||
default="hlr.sqlite3",
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
print("Loading SIM DB ...")
|
||||
sim_db = load_sim_db(opts.sim_db_filename)
|
||||
|
||||
for iccid, sim in sim_db.items():
|
||||
write_key_hlr(opts, sim)
|
||||
|
||||
|
||||
82
fairwaves_db_uniq.py
Executable file
82
fairwaves_db_uniq.py
Executable file
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to remove duplicates from a Fairwaves SIM card DB file
|
||||
#
|
||||
# Copyright (C) 2017-2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
|
||||
#from pySim.utils import h2b
|
||||
def h2b(s):
|
||||
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2])])
|
||||
|
||||
def load_sim_db(filename):
|
||||
sim_db = {}
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.reader(f, delimiter=' ')
|
||||
# Skip the header
|
||||
# reader.next()
|
||||
for l in reader:
|
||||
sim_db[l[0]] = l
|
||||
return sim_db
|
||||
|
||||
def write_sim_db(filename, sim_db):
|
||||
with open(filename, 'a') as f:
|
||||
cw = csv.writer(f, delimiter=' ')
|
||||
for iccid in sorted(sim_db.iterkeys()):
|
||||
cw.writerow([x for x in sim_db[iccid]])
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="Utility to remove duplicates from a Fairwaves SIM card DB file")
|
||||
|
||||
parser.add_option("-s", "--sim-db", dest="sim_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to load keys from (space separated)",
|
||||
default="sim_db.dat",
|
||||
)
|
||||
parser.add_option("-o", "--out-db", dest="out_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to write keys to (space separated)",
|
||||
default=None,
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
if opts.out_db_filename is None:
|
||||
print("Please specify output DB filename")
|
||||
sys.exit(1)
|
||||
|
||||
print("Loading SIM DB ...")
|
||||
sim_db = load_sim_db(opts.sim_db_filename)
|
||||
print("Writing SIM DB ...")
|
||||
write_sim_db(opts.out_db_filename, sim_db)
|
||||
289
pySim-fairwaves-prog.py
Executable file
289
pySim-fairwaves-prog.py
Executable file
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# Utility to update SPN field of a SIM card
|
||||
#
|
||||
# Copyright (C) 2017-2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import sys
|
||||
import csv
|
||||
import random
|
||||
import subprocess
|
||||
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, derive_milenage_opc
|
||||
from pySim.cards import card_autodetect
|
||||
|
||||
|
||||
def load_sim_db(filename):
|
||||
sim_db = {}
|
||||
with open(filename, 'r') as f:
|
||||
reader = csv.reader(f, delimiter=' ')
|
||||
# Skip the header
|
||||
reader.next()
|
||||
for l in reader:
|
||||
sim_db[l[0]] = l
|
||||
return sim_db
|
||||
|
||||
def write_params_csv(filename, sim_keys):
|
||||
with open(filename, 'a') as f:
|
||||
cw = csv.writer(f, delimiter=' ')
|
||||
cw.writerow([x for x in sim_keys])
|
||||
|
||||
|
||||
def program_sim_card(card, sim_db, opts):
|
||||
# Program the card
|
||||
print("Reading SIM card ...")
|
||||
|
||||
# EF.ICCID
|
||||
(iccid, sw) = card.read_iccid()
|
||||
if sw != '9000':
|
||||
print("ICCID: Can't read, response code = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
print("ICCID: %s" % (iccid))
|
||||
|
||||
# Find SIM card keys in the DB
|
||||
sim_keys = sim_db.get(iccid+'F')
|
||||
if sim_keys == None:
|
||||
print("Can't find SIM card in the SIM DB.")
|
||||
sys.exit(1)
|
||||
|
||||
# EF.IMSI
|
||||
(imsi, sw) = card.read_imsi()
|
||||
if sw != '9000':
|
||||
print("IMSI: Can't read, response code = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
print("IMSI: %s" % (imsi))
|
||||
|
||||
# EF.SPN
|
||||
((name, hplmn_disp, oplmn_disp), sw) = card.read_spn()
|
||||
if sw == '9000':
|
||||
print("Service Provider Name: %s" % name)
|
||||
print(" display for HPLMN %s" % hplmn_disp)
|
||||
print(" display for other PLMN %s" % oplmn_disp)
|
||||
else:
|
||||
print("Old SPN: Can't read, response code = %s" % (sw,))
|
||||
|
||||
print("Entring ADM code...")
|
||||
|
||||
# Enter ADM code to get access to proprietary files
|
||||
sw = card.verify_adm(h2b(sim_keys[6]))
|
||||
if sw != '9000':
|
||||
print("Fail to verify ADM code with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Read EF.Ki
|
||||
(ki, sw) = card.read_ki()
|
||||
if sw == '9000':
|
||||
ki = ki.upper()
|
||||
print("Ki: %s" % ki)
|
||||
else:
|
||||
print("Ki: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Read EF.OP/OPC
|
||||
((op_opc_type, op_opc), sw) = card.read_op_opc()
|
||||
if sw == '9000':
|
||||
op_opc = op_opc.upper()
|
||||
print("%s: %s" % (op_opc_type, op_opc))
|
||||
else:
|
||||
print("Ki: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Read EF.A3A8
|
||||
(a3a8, sw) = card.read_a3a8()
|
||||
if sw == '9000':
|
||||
print("A3/A8: %s" % (a3a8,))
|
||||
else:
|
||||
print("A3/A8: Can't read, response code = %s" % (sw,))
|
||||
|
||||
print("Programming...")
|
||||
|
||||
# Update SPN
|
||||
sw = card.update_spn(opts.name, False, False)
|
||||
if sw != '9000':
|
||||
print("SPN: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Update Ki
|
||||
ki = sim_keys[8]
|
||||
# ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)]).upper()
|
||||
# sim_keys[8] = ki
|
||||
sw = card.update_ki(sim_keys[8])
|
||||
if sw != '9000':
|
||||
print("Ki: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Update OPC
|
||||
op_opc = sim_keys[9][2:]
|
||||
# op_opc = derive_milenage_opc(ki, opts.op).upper()
|
||||
# sim_keys[9] = '01' + op_opc
|
||||
sw = card.update_opc(sim_keys[9][2:])
|
||||
if sw != '9000':
|
||||
print("OPC: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Update Home PLMN
|
||||
sw = card.update_hplmn_act(opts.mcc, opts.mnc)
|
||||
if sw != '9000':
|
||||
print("MCC/MNC: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Update IMSI
|
||||
imsi = sim_keys[1]
|
||||
# imsi = "%03d%02d%s" % (opts.mcc, opts.mnc, imsi[5:])
|
||||
# sim_keys[1] = imsi
|
||||
sw = card.update_imsi(imsi)
|
||||
if sw != '9000':
|
||||
print("IMSI: Fail to update with result = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
|
||||
# Verify EF.IMSI
|
||||
(imsi_new, sw) = card.read_imsi()
|
||||
if sw != '9000':
|
||||
print("IMSI: Can't read, response code = %s" % (sw,))
|
||||
sys.exit(1)
|
||||
print("IMSI: %s" % (imsi_new))
|
||||
|
||||
# Verify EF.SPN
|
||||
((name, hplmn_disp, oplmn_disp), sw) = card.read_spn()
|
||||
if sw == '9000':
|
||||
print("Service Provider Name: %s" % name)
|
||||
print(" display for HPLMN %s" % hplmn_disp)
|
||||
print(" display for other PLMN %s" % oplmn_disp)
|
||||
else:
|
||||
print("New SPN: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Verify EF.Ki
|
||||
(ki_new, sw) = card.read_ki()
|
||||
if sw == '9000':
|
||||
ki_new = ki_new.upper()
|
||||
print("Ki: %s (%s)" % (ki_new, "match" if (ki==ki_new) else ("DON'T match %s" % ki)))
|
||||
else:
|
||||
print("New Ki: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Verify EF.OP/OPC
|
||||
((op_opc_type_new, op_opc_new), sw) = card.read_op_opc()
|
||||
if sw == '9000':
|
||||
op_opc_new = op_opc_new.upper()
|
||||
print("%s: %s (%s)" % (op_opc_type_new, op_opc_new, "match" if (op_opc==op_opc_new) else ("DON'T match %s" % op_opc)))
|
||||
else:
|
||||
print("Ki: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# Done with this card
|
||||
print "Done !\n"
|
||||
|
||||
return sim_keys
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="An example utility to program Fairwaves SIM cards."
|
||||
" Modify it to your own specific needs.")
|
||||
|
||||
parser.add_option("-d", "--device", dest="device", metavar="DEV",
|
||||
help="Serial Device for SIM access [default: %default]",
|
||||
default="/dev/ttyUSB0",
|
||||
)
|
||||
parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD",
|
||||
help="Baudrate used for SIM access [default: %default]",
|
||||
default=9600,
|
||||
)
|
||||
parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC",
|
||||
help="Which PC/SC reader number for SIM access",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("-s", "--sim-db", dest="sim_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to load keys from (space searated)",
|
||||
default="sim_db.dat",
|
||||
)
|
||||
parser.add_option("-o", "--out-db", dest="out_db_filename", type='string', metavar="FILE",
|
||||
help="filename of a SIM DB to write keys to (space searated)",
|
||||
default="out.csv",
|
||||
)
|
||||
parser.add_option("--batch", dest="batch",
|
||||
help="Process SIM cards in batch mode - don't exit after programming and wait for the next SIM card to be inserted.",
|
||||
default=False, action="store_true",
|
||||
)
|
||||
parser.add_option("--sound", dest="sound_file", type='string', metavar="SOUND_FILE",
|
||||
help="Only in the batch mode. Play the given sound file on successful SIM programming",
|
||||
)
|
||||
parser.add_option("-n", "--name", dest="name",
|
||||
help="Operator name [default: %default]",
|
||||
default="Fairwaves",
|
||||
)
|
||||
parser.add_option("-x", "--mcc", dest="mcc", type="int",
|
||||
help="Mobile Country Code [default: %default]",
|
||||
default=001,
|
||||
)
|
||||
parser.add_option("-y", "--mnc", dest="mnc", type="int",
|
||||
help="Mobile Network Code [default: %default]",
|
||||
default=01,
|
||||
)
|
||||
parser.add_option("--op", dest="op",
|
||||
help="Set OP to derive OPC from OP and KI [default: %default]",
|
||||
default='00000000000000000000000000000000',
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
# Connect to the card
|
||||
if opts.pcsc_dev is None:
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
else:
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
|
||||
# Create command layer
|
||||
scc = SimCardCommands(transport=sl)
|
||||
|
||||
print("Loading SIM DB ...")
|
||||
sim_db = load_sim_db(opts.sim_db_filename)
|
||||
|
||||
if opts.batch:
|
||||
print("Batch mode enabled! Press Ctrl-C to exit")
|
||||
|
||||
# Loop once in non-batch mode and loop forever in batch mode
|
||||
first_run = True
|
||||
while first_run or opts.batch:
|
||||
print("Insert a SIM card to program...")
|
||||
sl.wait_for_card(newcardonly=not first_run)
|
||||
first_run = False
|
||||
|
||||
card = card_autodetect(scc)
|
||||
if card is None:
|
||||
print("Card autodetect failed")
|
||||
continue
|
||||
print "Autodetected card type %s" % card.name
|
||||
|
||||
sim_keys = program_sim_card(card, sim_db, opts)
|
||||
write_params_csv(opts.out_db_filename, sim_keys)
|
||||
if opts.sound_file is not None and opts.sound_file != "":
|
||||
subprocess.call(["paplay", opts.sound_file])
|
||||
108
pySim-prog.py
108
pySim-prog.py
@@ -39,8 +39,8 @@ except ImportError:
|
||||
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.cards import _cards_classes
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn, dec_iccid
|
||||
from pySim.ts_51_011 import EF
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn
|
||||
|
||||
|
||||
def parse_options():
|
||||
|
||||
@@ -58,18 +58,10 @@ def parse_options():
|
||||
help="Which PC/SC reader number for SIM access",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",
|
||||
help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("-t", "--type", dest="type",
|
||||
help="Card type (user -t list to view) [default: %default]",
|
||||
default="auto",
|
||||
)
|
||||
parser.add_option("-T", "--probe", dest="probe",
|
||||
help="Determine card type",
|
||||
default=False, action="store_true"
|
||||
)
|
||||
parser.add_option("-a", "--pin-adm", dest="pin_adm",
|
||||
help="ADM PIN used for provisioning (overwrites default)",
|
||||
)
|
||||
@@ -101,7 +93,7 @@ def parse_options():
|
||||
default=55,
|
||||
)
|
||||
parser.add_option("-m", "--smsc", dest="smsc",
|
||||
help="SMSC number (Start with + for international no.) [default: '00 + country code + 5555']",
|
||||
help="SMSP [default: '00 + country code + 5555']",
|
||||
)
|
||||
parser.add_option("-M", "--smsp", dest="smsp",
|
||||
help="Raw SMSP content in hex [default: auto from SMSC]",
|
||||
@@ -128,9 +120,6 @@ def parse_options():
|
||||
parser.add_option("--read-imsi", dest="read_imsi", action="store_true",
|
||||
help="Read the IMSI from the CARD", default=False
|
||||
)
|
||||
parser.add_option("--read-iccid", dest="read_iccid", action="store_true",
|
||||
help="Read the ICCID from the CARD", default=False
|
||||
)
|
||||
parser.add_option("-z", "--secret", dest="secret", metavar="STR",
|
||||
help="Secret used for ICCID/IMSI autogen",
|
||||
)
|
||||
@@ -167,12 +156,9 @@ def parse_options():
|
||||
print kls.name
|
||||
sys.exit(0)
|
||||
|
||||
if options.probe:
|
||||
return options
|
||||
|
||||
if options.source == 'csv':
|
||||
if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False) and (options.read_iccid is False):
|
||||
parser.error("CSV mode needs either an IMSI, --read-imsi, --read-iccid or batch mode")
|
||||
if (options.imsi is None) and (options.batch_mode is False) and (options.read_imsi is False):
|
||||
parser.error("CSV mode needs either an IMSI, --read-imsi or batch mode")
|
||||
if options.read_csv is None:
|
||||
parser.error("CSV mode requires a CSV input file")
|
||||
elif options.source == 'cmdline':
|
||||
@@ -268,8 +254,8 @@ def gen_parameters(opts):
|
||||
# ICCID (19 digits, E.118), though some phase1 vendors use 20 :(
|
||||
if opts.iccid is not None:
|
||||
iccid = opts.iccid
|
||||
if not _isnum(iccid, 19) and not _isnum(iccid, 20):
|
||||
raise ValueError('ICCID must be 19 or 20 digits !');
|
||||
if not _isnum(iccid, 19):
|
||||
raise ValueError('ICCID must be 19 digits !');
|
||||
|
||||
else:
|
||||
if opts.num is None:
|
||||
@@ -326,19 +312,14 @@ def gen_parameters(opts):
|
||||
raise ValueError('SMSP must be at least 28 bytes')
|
||||
|
||||
else:
|
||||
ton = "81"
|
||||
if opts.smsc is not None:
|
||||
smsc = opts.smsc
|
||||
if smsc[0] == '+':
|
||||
ton = "91"
|
||||
smsc = smsc[1:]
|
||||
if not _isnum(smsc):
|
||||
raise ValueError('SMSC must be digits only!\n \
|
||||
Start with \'+\' for international numbers')
|
||||
raise ValueError('SMSC must be digits only !')
|
||||
else:
|
||||
smsc = '00%d' % opts.country + '5555' # Hack ...
|
||||
|
||||
smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + swap_nibbles(rpad(smsc, 20))
|
||||
smsc = '%02d' % ((len(smsc) + 3)//2,) + "81" + swap_nibbles(rpad(smsc, 20))
|
||||
|
||||
smsp = (
|
||||
'e1' + # Parameters indicator
|
||||
@@ -380,13 +361,9 @@ def gen_parameters(opts):
|
||||
opc = ''.join(['%02x' % random.randrange(0,256) for i in range(16)])
|
||||
|
||||
if opts.pin_adm is not None:
|
||||
if len(opts.pin_adm) <= 8:
|
||||
pin_adm = ''.join(['%02x'%(ord(x)) for x in opts.pin_adm])
|
||||
pin_adm = rpad(pin_adm, 16)
|
||||
elif len(opts.pin_adm) == 16:
|
||||
pin_adm = opts.pin_adm
|
||||
else:
|
||||
raise ValueError("PIN-ADM needs to be <=8 digits (ascii) or exactly 16 digits (raw hex)")
|
||||
pin_adm = opts.pin_adm
|
||||
if not re.match('^([0-9a-fA-F][0-9a-fA-F])+$', pin_adm):
|
||||
raise ValueError('ADM pin needs to be in hex format (even number of hex digits)')
|
||||
else:
|
||||
pin_adm = None
|
||||
|
||||
@@ -402,7 +379,7 @@ def gen_parameters(opts):
|
||||
'ki' : ki,
|
||||
'opc' : opc,
|
||||
'acc' : acc,
|
||||
'adm1' : pin_adm,
|
||||
'pin_adm' : pin_adm,
|
||||
}
|
||||
|
||||
|
||||
@@ -417,7 +394,6 @@ def print_parameters(params):
|
||||
> Ki : %(ki)s
|
||||
> OPC : %(opc)s
|
||||
> ACC : %(acc)s
|
||||
> ADM1 : %(adm1)s
|
||||
""" % params
|
||||
|
||||
|
||||
@@ -431,23 +407,18 @@ def write_params_csv(opts, params):
|
||||
cw.writerow([params[x] for x in row])
|
||||
f.close()
|
||||
|
||||
def _read_params_csv(opts, iccid=None, imsi=None):
|
||||
def _read_params_csv(opts, imsi):
|
||||
import csv
|
||||
row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki', 'opc']
|
||||
f = open(opts.read_csv, 'r')
|
||||
cr = csv.DictReader(f)
|
||||
cr = csv.DictReader(f, row)
|
||||
i = 0
|
||||
if not 'iccid' in cr.fieldnames:
|
||||
raise Exception("CSV file in wrong format!")
|
||||
for row in cr:
|
||||
if opts.num is not None and opts.read_iccid is False and opts.read_imsi is False:
|
||||
if opts.num is not None and opts.read_imsi is False:
|
||||
if opts.num == i:
|
||||
f.close()
|
||||
return row;
|
||||
i += 1
|
||||
if row['iccid'] == iccid:
|
||||
f.close()
|
||||
return row;
|
||||
|
||||
if row['imsi'] == imsi:
|
||||
f.close()
|
||||
return row;
|
||||
@@ -455,8 +426,8 @@ def _read_params_csv(opts, iccid=None, imsi=None):
|
||||
f.close()
|
||||
return None
|
||||
|
||||
def read_params_csv(opts, imsi=None, iccid=None):
|
||||
row = _read_params_csv(opts, iccid=iccid, imsi=imsi)
|
||||
def read_params_csv(opts, imsi):
|
||||
row = _read_params_csv(opts, imsi)
|
||||
if row is not None:
|
||||
row['mcc'] = int(row['mcc'])
|
||||
row['mnc'] = int(row['mnc'])
|
||||
@@ -550,7 +521,7 @@ def card_detect(opts, scc):
|
||||
for kls in _cards_classes:
|
||||
card = kls.autodetect(scc)
|
||||
if card:
|
||||
print "Autodetected card type: %s" % card.name
|
||||
print "Autodetected card type %s" % card.name
|
||||
card.reset()
|
||||
break
|
||||
|
||||
@@ -565,7 +536,7 @@ def card_detect(opts, scc):
|
||||
card = ctypes[opts.type](scc)
|
||||
|
||||
else:
|
||||
raise ValueError("Unknown card type: %s" % opts.type)
|
||||
raise ValueError("Unknown card type %s" % opts.type)
|
||||
|
||||
return card
|
||||
|
||||
@@ -575,22 +546,13 @@ if __name__ == '__main__':
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
# Init card reader driver
|
||||
if opts.pcsc_dev is not None:
|
||||
print("Using PC/SC reader (dev=%d) interface"
|
||||
% opts.pcsc_dev)
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
elif opts.osmocon_sock is not None:
|
||||
print("Using Calypso-based (OsmocomBB, sock=%s) reader interface"
|
||||
% opts.osmocon_sock)
|
||||
from pySim.transport.calypso import CalypsoSimLink
|
||||
sl = CalypsoSimLink(sock_path=opts.osmocon_sock)
|
||||
else: # Serial reader is default
|
||||
print("Using serial reader (port=%s, baudrate=%d) interface"
|
||||
% (opts.device, opts.baudrate))
|
||||
# Connect to the card
|
||||
if opts.pcsc_dev is None:
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
else:
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
|
||||
# Create command layer
|
||||
scc = SimCardCommands(transport=sl)
|
||||
@@ -623,10 +585,6 @@ if __name__ == '__main__':
|
||||
else:
|
||||
sys.exit(-1)
|
||||
|
||||
# Probe only
|
||||
if opts.probe:
|
||||
break;
|
||||
|
||||
# Erase if requested
|
||||
if opts.erase:
|
||||
print "Formatting ..."
|
||||
@@ -637,17 +595,7 @@ if __name__ == '__main__':
|
||||
if opts.source == 'cmdline':
|
||||
cp = gen_parameters(opts)
|
||||
elif opts.source == 'csv':
|
||||
imsi = None
|
||||
iccid = None
|
||||
if opts.read_iccid:
|
||||
if opts.dry_run:
|
||||
# Connect transport
|
||||
print "Insert card now (or CTRL-C to cancel)"
|
||||
sl.wait_for_card(newcardonly=not first)
|
||||
(res,_) = scc.read_binary(['3f00', '2fe2'], length=10)
|
||||
iccid = dec_iccid(res)
|
||||
print iccid
|
||||
elif opts.read_imsi:
|
||||
if opts.read_imsi:
|
||||
if opts.dry_run:
|
||||
# Connect transport
|
||||
print "Insert card now (or CTRL-C to cancel)"
|
||||
@@ -656,7 +604,7 @@ if __name__ == '__main__':
|
||||
imsi = swap_nibbles(res)[3:]
|
||||
else:
|
||||
imsi = opts.imsi
|
||||
cp = read_params_csv(opts, imsi=imsi, iccid=iccid)
|
||||
cp = read_params_csv(opts, imsi)
|
||||
if cp is None:
|
||||
print "Error reading parameters\n"
|
||||
sys.exit(2)
|
||||
|
||||
126
pySim-read-all.py
Executable file
126
pySim-read-all.py
Executable file
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
#
|
||||
# Utility to display all files from a SIM card
|
||||
#
|
||||
#
|
||||
# Copyright (C) 2009 Sylvain Munaut <tnt@246tNt.com>
|
||||
# Copyright (C) 2010 Harald Welte <laforge@gnumonks.org>
|
||||
# Copyright (C) 2013 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import hashlib
|
||||
from optparse import OptionParser
|
||||
import os
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
# Python < 2.5
|
||||
import simplejson as json
|
||||
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, dec_select_ef_response
|
||||
from pySim.ts_51_011 import EF, DF
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]")
|
||||
|
||||
parser.add_option("-d", "--device", dest="device", metavar="DEV",
|
||||
help="Serial Device for SIM access [default: %default]",
|
||||
default="/dev/ttyUSB0",
|
||||
)
|
||||
parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD",
|
||||
help="Baudrate used for SIM access [default: %default]",
|
||||
default=9600,
|
||||
)
|
||||
parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC",
|
||||
help="Which PC/SC reader number for SIM access",
|
||||
default=None,
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
# Connect to the card
|
||||
if opts.pcsc_dev is None:
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
else:
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
|
||||
# Create command layer
|
||||
scc = SimCardCommands(transport=sl)
|
||||
|
||||
# Wait for SIM card
|
||||
sl.wait_for_card()
|
||||
|
||||
# Program the card
|
||||
print("Reading ...")
|
||||
|
||||
# Read all
|
||||
for (name, path) in EF.items():
|
||||
try:
|
||||
resp = scc.select_file(path)
|
||||
(length, file_id, file_type, increase_cmd, access_cond,
|
||||
file_status, data_len, ef_struct, record_len) = dec_select_ef_response(resp[-1])
|
||||
# print name, resp
|
||||
print name, (length, file_id, file_type, increase_cmd, access_cond, file_status, data_len, ef_struct, record_len)
|
||||
|
||||
if not access_cond[0] == '0' and not access_cond[0] == '1':
|
||||
print("%s: Requires %s access to read." % (name, access_cond[0],))
|
||||
continue
|
||||
|
||||
if ef_struct == '00':
|
||||
# transparent
|
||||
(res, sw) = scc.read_binary_selected(length)
|
||||
if sw == '9000':
|
||||
print("%s: %s" % (name, res,))
|
||||
else:
|
||||
print("%s: Can't read, response code = %s" % (name, sw,))
|
||||
elif (ef_struct == '01' or ef_struct == '03') and record_len>0:
|
||||
for i in range(1,length/record_len+1):
|
||||
# linear fixed
|
||||
(res, sw) = scc.read_record_selected(record_len, i)
|
||||
if sw == '9000':
|
||||
print("%s[%d]: %s" % (name, i, res,))
|
||||
else:
|
||||
print("%s[%d]: Can't read, response code = %s" % (name, i, sw,))
|
||||
elif ef_struct == '03':
|
||||
# cyclic
|
||||
raise RuntimeError("Don't know how to read a cyclic EF")
|
||||
else:
|
||||
raise RuntimeError("Unknown EF type")
|
||||
except RuntimeError as e:
|
||||
print("%s: Can't read (%s)" % (name,e.message,))
|
||||
|
||||
# Done for this card and maybe for everything ?
|
||||
print "Done !\n"
|
||||
@@ -28,7 +28,6 @@ import os
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
from pySim.ts_51_011 import EF, DF
|
||||
|
||||
try:
|
||||
import json
|
||||
@@ -37,8 +36,8 @@ except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
from pySim.commands import SimCardCommands
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid, format_xplmn_w_act
|
||||
|
||||
from pySim.utils import h2b, swap_nibbles, rpad, dec_imsi, dec_iccid
|
||||
from pySim.ts_51_011 import EF, DF
|
||||
|
||||
def parse_options():
|
||||
|
||||
@@ -56,10 +55,6 @@ def parse_options():
|
||||
help="Which PC/SC reader number for SIM access",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("--osmocon", dest="osmocon_sock", metavar="PATH",
|
||||
help="Socket path for Calypso (e.g. Motorola C1XX) based reader (via OsmocomBB)",
|
||||
default=None,
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
@@ -74,22 +69,13 @@ if __name__ == '__main__':
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
# Init card reader driver
|
||||
if opts.pcsc_dev is not None:
|
||||
print("Using PC/SC reader (dev=%d) interface"
|
||||
% opts.pcsc_dev)
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
elif opts.osmocon_sock is not None:
|
||||
print("Using Calypso-based (OsmocomBB, sock=%s) reader interface"
|
||||
% opts.osmocon_sock)
|
||||
from pySim.transport.calypso import CalypsoSimLink
|
||||
sl = CalypsoSimLink(sock_path=opts.osmocon_sock)
|
||||
else: # Serial reader is default
|
||||
print("Using serial reader (port=%s, baudrate=%d) interface"
|
||||
% (opts.device, opts.baudrate))
|
||||
# Connect to the card
|
||||
if opts.pcsc_dev is None:
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
else:
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
|
||||
# Create command layer
|
||||
scc = SimCardCommands(transport=sl)
|
||||
@@ -101,7 +87,7 @@ if __name__ == '__main__':
|
||||
print("Reading ...")
|
||||
|
||||
# EF.ICCID
|
||||
(res, sw) = scc.read_binary(EF['ICCID'])
|
||||
(res, sw) = scc.read_binary(['3f00', '2fe2'])
|
||||
if sw == '9000':
|
||||
print("ICCID: %s" % (dec_iccid(res),))
|
||||
else:
|
||||
@@ -121,45 +107,21 @@ if __name__ == '__main__':
|
||||
else:
|
||||
print("SMSP: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# EF.PLMNsel
|
||||
try:
|
||||
(res, sw) = scc.read_binary(EF['PLMNsel'])
|
||||
if sw == '9000':
|
||||
print("PLMNsel: %s" % (res))
|
||||
else:
|
||||
print("PLMNsel: Can't read, response code = %s" % (sw,))
|
||||
except Exception as e:
|
||||
print "HPLMNAcT: Can't read file -- " + str(e)
|
||||
# EF.SPN
|
||||
(res, sw) = scc.read_binary(EF['SPN'])
|
||||
if sw == '9000':
|
||||
print("SPN: %s" % (res,))
|
||||
else:
|
||||
print("SPN: Can't read, response code = %s" % (sw,))
|
||||
|
||||
# EF.PLMNwAcT
|
||||
try:
|
||||
(res, sw) = scc.read_binary(EF['PLMNwAcT'])
|
||||
if sw == '9000':
|
||||
print("PLMNwAcT:\n%s" % (format_xplmn_w_act(res)))
|
||||
else:
|
||||
print("PLMNwAcT: Can't read, response code = %s" % (sw,))
|
||||
except Exception as e:
|
||||
print "PLMNwAcT: Can't read file -- " + str(e)
|
||||
|
||||
# EF.OPLMNwAcT
|
||||
try:
|
||||
(res, sw) = scc.read_binary(EF['OPLMNwAcT'])
|
||||
if sw == '9000':
|
||||
print("OPLMNwAcT:\n%s" % (format_xplmn_w_act(res)))
|
||||
else:
|
||||
print("OPLMNwAcT: Can't read, response code = %s" % (sw,))
|
||||
except Exception as e:
|
||||
print "OPLMNwAcT: Can't read file -- " + str(e)
|
||||
|
||||
# EF.HPLMNAcT
|
||||
try:
|
||||
(res, sw) = scc.read_binary(EF['HPLMNAcT'])
|
||||
if sw == '9000':
|
||||
print("HPLMNAcT:\n%s" % (format_xplmn_w_act(res)))
|
||||
else:
|
||||
print("HPLMNAcT: Can't read, response code = %s" % (sw,))
|
||||
except Exception as e:
|
||||
print "HPLMNAcT: Can't read file -- " + str(e)
|
||||
# EF.HPLMN
|
||||
(res, sw) = scc.read_binary(EF['PLMNsel'])
|
||||
if sw == '9000':
|
||||
print("HPLMN: %s" % (res))
|
||||
# print("HPLMN: %s" % (dec_hplmn(res),))
|
||||
else:
|
||||
print("HPLMN: Can't read, response code = %s" % (sw,))
|
||||
# FIXME
|
||||
|
||||
# EF.ACC
|
||||
(res, sw) = scc.read_binary(['3f00', '7f20', '6f78'])
|
||||
@@ -170,24 +132,17 @@ if __name__ == '__main__':
|
||||
|
||||
# EF.MSISDN
|
||||
try:
|
||||
# print(scc.record_size(['3f00', '7f10', '6f40']))
|
||||
(res, sw) = scc.read_record(['3f00', '7f10', '6f40'], 1)
|
||||
# print(scc.record_size(EF['MSISDN']))
|
||||
(res, sw) = scc.read_record(EF['MSISDN'], 1)
|
||||
if sw == '9000':
|
||||
if res[1] != 'f':
|
||||
print("MSISDN: %s" % (res,))
|
||||
else:
|
||||
print("MSISDN: Not available")
|
||||
print("MSISDN: %s (Not available)" % (res,))
|
||||
else:
|
||||
print("MSISDN: Can't read, response code = %s" % (sw,))
|
||||
except Exception as e:
|
||||
print "MSISDN: Can't read file -- " + str(e)
|
||||
|
||||
# EF.AD
|
||||
(res, sw) = scc.read_binary(['3f00', '7f20', '6fad'])
|
||||
if sw == '9000':
|
||||
print("AD: %s" % (res,))
|
||||
else:
|
||||
print("AD: Can't read, response code = %s" % (sw,))
|
||||
except:
|
||||
print "MSISDN: Can't read. Probably not existing file"
|
||||
|
||||
# Done for this card and maybe for everything ?
|
||||
print "Done !\n"
|
||||
|
||||
97
pySim-run-gsm.py
Executable file
97
pySim-run-gsm.py
Executable file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python2
|
||||
|
||||
#
|
||||
# Utility to run an A3/A8 algorithm on a SIM card
|
||||
#
|
||||
# Copyright (C) 2018 Alexander Chemeris <alexander.chemeris@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
from pySim.commands import SimCardCommands
|
||||
|
||||
def parse_options():
|
||||
|
||||
parser = OptionParser(usage="usage: %prog [options]",
|
||||
description="Utility to run an A3/A8 algorithm on a SIM card. "
|
||||
"Prints generated SRES and Kc for a given RAND number "
|
||||
"and exits.")
|
||||
|
||||
parser.add_option("-d", "--device", dest="device", metavar="DEV",
|
||||
help="Serial Device for SIM access [default: %default]",
|
||||
default="/dev/ttyUSB0",
|
||||
)
|
||||
parser.add_option("-b", "--baud", dest="baudrate", type="int", metavar="BAUD",
|
||||
help="Baudrate used for SIM access [default: %default]",
|
||||
default=9600,
|
||||
)
|
||||
parser.add_option("-p", "--pcsc-device", dest="pcsc_dev", type='int', metavar="PCSC",
|
||||
help="Which PC/SC reader number for SIM access",
|
||||
default=None,
|
||||
)
|
||||
parser.add_option("-r", "--rand", dest="rand", metavar="RAND",
|
||||
help="16 bytes of RAND value",
|
||||
default=None,
|
||||
)
|
||||
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if args:
|
||||
parser.error("Extraneous arguments")
|
||||
|
||||
return options
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
# Parse options
|
||||
opts = parse_options()
|
||||
|
||||
if opts.rand is None:
|
||||
print("Please specify RAND value")
|
||||
sys.exit(1)
|
||||
if len(opts.rand) != 32:
|
||||
print("RAND must be 16 bytes long")
|
||||
sys.exit(1)
|
||||
|
||||
# Connect to the card
|
||||
if opts.pcsc_dev is None:
|
||||
from pySim.transport.serial import SerialSimLink
|
||||
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
|
||||
else:
|
||||
from pySim.transport.pcsc import PcscSimLink
|
||||
sl = PcscSimLink(opts.pcsc_dev)
|
||||
|
||||
# Create command layer
|
||||
scc = SimCardCommands(transport=sl)
|
||||
|
||||
# Wait for SIM card
|
||||
sl.wait_for_card()
|
||||
|
||||
# Program the card
|
||||
print("Running GSM algorithm with RAND %s" % (opts.rand,))
|
||||
|
||||
# Run GSM A3/A8
|
||||
(res, sw) = scc.run_gsm(opts.rand)
|
||||
if sw == '9000':
|
||||
sres, kc = res
|
||||
print("SRES = %s" % (sres,))
|
||||
print("Kc = %s" % (kc,))
|
||||
else:
|
||||
print("Error %s, result data '%s'" % (sw, res))
|
||||
|
||||
# Done for this card and maybe for everything ?
|
||||
print "Done !\n"
|
||||
292
pySim/cards.py
292
pySim/cards.py
@@ -83,55 +83,10 @@ class Card(object):
|
||||
data, sw = self._scc.update_binary(EF['HPLMNwAcT'], content + 'ffffff0000' * (size/5-1))
|
||||
return sw
|
||||
|
||||
def update_oplmn_act(self, mcc, mnc, access_tech='FFFF'):
|
||||
"""
|
||||
See note in update_hplmn_act()
|
||||
"""
|
||||
# get size and write EF.OPLMNwAcT
|
||||
data = self._scc.read_binary(EF['OPLMNwAcT'], length=None, offset=0)
|
||||
size = len(data[0])/2
|
||||
hplmn = enc_plmn(mcc, mnc)
|
||||
content = hplmn + access_tech
|
||||
data, sw = self._scc.update_binary(EF['OPLMNwAcT'], content + 'ffffff0000' * (size/5-1))
|
||||
return sw
|
||||
|
||||
def update_plmn_act(self, mcc, mnc, access_tech='FFFF'):
|
||||
"""
|
||||
See note in update_hplmn_act()
|
||||
"""
|
||||
# get size and write EF.PLMNwAcT
|
||||
data = self._scc.read_binary(EF['PLMNwAcT'], length=None, offset=0)
|
||||
size = len(data[0])/2
|
||||
hplmn = enc_plmn(mcc, mnc)
|
||||
content = hplmn + access_tech
|
||||
data, sw = self._scc.update_binary(EF['PLMNwAcT'], content + 'ffffff0000' * (size/5-1))
|
||||
return sw
|
||||
|
||||
def update_plmnsel(self, mcc, mnc):
|
||||
data = self._scc.read_binary(EF['PLMNsel'], length=None, offset=0)
|
||||
size = len(data[0])/2
|
||||
hplmn = enc_plmn(mcc, mnc)
|
||||
data, sw = self._scc.update_binary(EF['PLMNsel'], hplmn + 'ff' * (size-3))
|
||||
return sw
|
||||
|
||||
def update_smsp(self, smsp):
|
||||
data, sw = self._scc.update_record(EF['SMSP'], 1, rpad(smsp, 84))
|
||||
return sw
|
||||
|
||||
def update_ad(self, mnc):
|
||||
#See also: 3GPP TS 31.102, chapter 4.2.18
|
||||
mnclen = len(str(mnc))
|
||||
if mnclen == 1:
|
||||
mnclen = 2
|
||||
if mnclen > 3:
|
||||
raise RuntimeError('unable to calculate proper mnclen')
|
||||
|
||||
data = self._scc.read_binary(EF['AD'], length=None, offset=0)
|
||||
size = len(data[0])/2
|
||||
content = data[0][0:6] + "%02X" % mnclen
|
||||
data, sw = self._scc.update_binary(EF['AD'], content)
|
||||
return sw
|
||||
|
||||
def read_spn(self):
|
||||
(spn, sw) = self._scc.read_binary(EF['SPN'])
|
||||
if sw == '9000':
|
||||
@@ -338,12 +293,12 @@ class FakeMagicSim(Card):
|
||||
|
||||
# Set first entry
|
||||
entry = (
|
||||
'81' + # 1b Status: Valid & Active
|
||||
'81' + # 1b Status: Valid & Active
|
||||
rpad(b2h(p['name'][0:14]), 28) + # 14b Entry Name
|
||||
enc_iccid(p['iccid']) + # 10b ICCID
|
||||
enc_imsi(p['imsi']) + # 9b IMSI_len + id_type(9) + IMSI
|
||||
p['ki'] + # 16b Ki
|
||||
lpad(p['smsp'], 80) # 40b SMSP (padded with ff if needed)
|
||||
enc_iccid(p['iccid']) + # 10b ICCID
|
||||
enc_imsi(p['imsi']) + # 9b IMSI_len + id_type(9) + IMSI
|
||||
p['ki'] + # 16b Ki
|
||||
lpad(p['smsp'], 80) # 40b SMSP (padded with ff if needed)
|
||||
)
|
||||
self._scc.update_record('000c', 1, entry)
|
||||
|
||||
@@ -375,8 +330,8 @@ class GrcardSim(Card):
|
||||
#self._scc.verify_chv(4, h2b("4444444444444444"))
|
||||
|
||||
# Authenticate using ADM PIN 5
|
||||
if p['adm1']:
|
||||
pin = h2b(p['adm1'])
|
||||
if p['pin_adm']:
|
||||
pin = p['pin_adm']
|
||||
else:
|
||||
pin = h2b("4444444444444444")
|
||||
self._scc.verify_chv(5, pin)
|
||||
@@ -394,9 +349,8 @@ class GrcardSim(Card):
|
||||
data, sw = self._scc.update_binary('6f78', lpad(p['acc'], 4))
|
||||
|
||||
# EF.SMSP
|
||||
if p.get('smsp'):
|
||||
r = self._scc.select_file(['3f00', '7f10', '6f42'])
|
||||
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80))
|
||||
r = self._scc.select_file(['3f00', '7f10', '6f42'])
|
||||
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80))
|
||||
|
||||
# Set the Ki using proprietary command
|
||||
pdu = '80d4020010' + p['ki']
|
||||
@@ -426,15 +380,6 @@ class SysmoSIMgr1(GrcardSim):
|
||||
"""
|
||||
name = 'sysmosim-gr1'
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
# Look for ATR
|
||||
if scc.get_atr() == toBytes("3B 99 18 00 11 88 22 33 44 55 66 77 60"):
|
||||
return kls(scc)
|
||||
except:
|
||||
return None
|
||||
return None
|
||||
|
||||
class SysmoUSIMgr1(Card):
|
||||
"""
|
||||
@@ -495,8 +440,8 @@ class SysmoSIMgr2(Card):
|
||||
# P1: 3A for PIN, 3B for PUK
|
||||
# P2: CHV number, as in VERIFY CHV for PIN, and as in UNBLOCK CHV for PUK
|
||||
# P3: 08, CHV length (curiously the PUK is also 08 length, instead of 10)
|
||||
if p['adm1']:
|
||||
pin = p['adm1']
|
||||
if p['pin_adm']:
|
||||
pin = p['pin_adm']
|
||||
else:
|
||||
pin = h2b("4444444444444444")
|
||||
|
||||
@@ -536,8 +481,7 @@ class SysmoSIMgr2(Card):
|
||||
r = self._scc.select_file(['3f00', '7f10'])
|
||||
|
||||
# write EF.SMSP
|
||||
if p.get('smsp'):
|
||||
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80))
|
||||
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 80))
|
||||
|
||||
def erase(self):
|
||||
return
|
||||
@@ -552,7 +496,7 @@ class SysmoUSIMSJS1(Card):
|
||||
def __init__(self, ssc):
|
||||
super(SysmoUSIMSJS1, self).__init__(ssc)
|
||||
self._scc.cla_byte = "00"
|
||||
self._scc.sel_ctrl = "0004" #request an FCP
|
||||
self._scc.sel_ctrl = "000C"
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
@@ -567,9 +511,9 @@ class SysmoUSIMSJS1(Card):
|
||||
def program(self, p):
|
||||
|
||||
# authenticate as ADM using default key (written on the card..)
|
||||
if not p['adm1']:
|
||||
if not p['pin_adm']:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
self._scc.verify_chv(0x0A, p['adm1'])
|
||||
self._scc.verify_chv(0x0A, h2b(p['pin_adm']))
|
||||
|
||||
# select MF
|
||||
r = self._scc.select_file(['3f00'])
|
||||
@@ -583,42 +527,17 @@ class SysmoUSIMSJS1(Card):
|
||||
# set Ki in proprietary file
|
||||
data, sw = self._scc.update_binary('00FF', p['ki'])
|
||||
|
||||
# set OPc in proprietary file
|
||||
if 'opc' in p:
|
||||
content = "01" + p['opc']
|
||||
data, sw = self._scc.update_binary('00F7', content)
|
||||
# set Ki in proprietary file
|
||||
content = "01" + p['opc']
|
||||
data, sw = self._scc.update_binary('00F7', content)
|
||||
|
||||
|
||||
# write EF.IMSI
|
||||
data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
|
||||
|
||||
# EF.PLMNsel
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmnsel(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNsel failed with code %s"%sw)
|
||||
|
||||
# EF.PLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNwAcT failed with code %s"%sw)
|
||||
|
||||
# EF.OPLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_oplmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming OPLMNwAcT failed with code %s"%sw)
|
||||
|
||||
# EF.AD
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_ad(p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming AD failed with code %s"%sw)
|
||||
|
||||
# EF.SMSP
|
||||
if p.get('smsp'):
|
||||
r = self._scc.select_file(['3f00', '7f10'])
|
||||
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True)
|
||||
r = self._scc.select_file(['3f00', '7f10'])
|
||||
data, sw = self._scc.update_record('6f42', 1, lpad(p['smsp'], 104), force_len=True)
|
||||
|
||||
def erase(self):
|
||||
return
|
||||
@@ -631,9 +550,17 @@ class FairwavesSIM(Card):
|
||||
The SIM card is operating according to the standard.
|
||||
For Ki/OP/OPC programming the following files are additionally open for writing:
|
||||
3F00/7F20/FF01 – OP/OPC:
|
||||
byte 1 = 0x01, bytes 2-17: OPC;
|
||||
byte 1 = 0x00, bytes 2-17: OP;
|
||||
byte 1 = 0x01, bytes 2-17: OPC;
|
||||
byte 1 = 0x00, bytes 2-17: OP;
|
||||
3F00/7F20/FF02: Ki
|
||||
3F00/7F20/FF03: 2G/3G auth algorithm
|
||||
byte 1 = GSM SIM A3/A8 algorithm selection
|
||||
byte 2 = USIM A3/A8 algorithm selection
|
||||
Algorithms:
|
||||
0x01 = Milenage
|
||||
0x03 = COMP128v1
|
||||
0x06 = COMP128v2
|
||||
0x07 = COMP128v3
|
||||
"""
|
||||
|
||||
name = 'Fairwaves SIM'
|
||||
@@ -641,10 +568,12 @@ class FairwavesSIM(Card):
|
||||
_EF_num = {
|
||||
'Ki': 'FF02',
|
||||
'OP/OPC': 'FF01',
|
||||
'A3A8': 'FF03',
|
||||
}
|
||||
_EF = {
|
||||
'Ki': DF['GSM']+[_EF_num['Ki']],
|
||||
'OP/OPC': DF['GSM']+[_EF_num['OP/OPC']],
|
||||
'A3A8': DF['GSM']+[_EF_num['A3A8']],
|
||||
}
|
||||
|
||||
def __init__(self, ssc):
|
||||
@@ -728,6 +657,14 @@ class FairwavesSIM(Card):
|
||||
data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
|
||||
return sw
|
||||
|
||||
def read_a3a8(self):
|
||||
(ef, sw) = self._scc.read_binary(self._EF['A3A8'])
|
||||
return (ef, sw)
|
||||
|
||||
def update_a3a8(self, content):
|
||||
(ef, sw) = self._scc.update_binary(self._EF['A3A8'], content)
|
||||
return (ef, sw)
|
||||
|
||||
|
||||
def program(self, p):
|
||||
# authenticate as ADM1
|
||||
@@ -768,153 +705,10 @@ class FairwavesSIM(Card):
|
||||
return
|
||||
|
||||
|
||||
class OpenCellsSim(Card):
|
||||
"""
|
||||
OpenCellsSim
|
||||
|
||||
"""
|
||||
|
||||
name = 'OpenCells SIM'
|
||||
|
||||
def __init__(self, ssc):
|
||||
super(OpenCellsSim, self).__init__(ssc)
|
||||
self._adm_chv_num = 0x0A
|
||||
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
# Look for ATR
|
||||
if scc.get_atr() == toBytes("3B 9F 95 80 1F C3 80 31 E0 73 FE 21 13 57 86 81 02 86 98 44 18 A8"):
|
||||
return kls(scc)
|
||||
except:
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
def program(self, p):
|
||||
if not p['pin_adm']:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
self._scc.verify_chv(0x0A, h2b(p['pin_adm']))
|
||||
|
||||
# select MF
|
||||
r = self._scc.select_file(['3f00'])
|
||||
|
||||
# write EF.ICCID
|
||||
data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid']))
|
||||
|
||||
r = self._scc.select_file(['7ff0'])
|
||||
|
||||
# set Ki in proprietary file
|
||||
data, sw = self._scc.update_binary('FF02', p['ki'])
|
||||
|
||||
# set OPC in proprietary file
|
||||
data, sw = self._scc.update_binary('FF01', p['opc'])
|
||||
|
||||
# select DF_GSM
|
||||
r = self._scc.select_file(['7f20'])
|
||||
|
||||
# write EF.IMSI
|
||||
data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
|
||||
|
||||
class WavemobileSim(Card):
|
||||
"""
|
||||
WavemobileSim
|
||||
|
||||
"""
|
||||
|
||||
name = 'Wavemobile-SIM'
|
||||
|
||||
def __init__(self, ssc):
|
||||
super(WavemobileSim, self).__init__(ssc)
|
||||
self._adm_chv_num = 0x0A
|
||||
self._scc.cla_byte = "00"
|
||||
self._scc.sel_ctrl = "0004" #request an FCP
|
||||
|
||||
@classmethod
|
||||
def autodetect(kls, scc):
|
||||
try:
|
||||
# Look for ATR
|
||||
if scc.get_atr() == toBytes("3B 9F 95 80 1F C7 80 31 E0 73 F6 21 13 67 4D 45 16 00 43 01 00 8F"):
|
||||
return kls(scc)
|
||||
except:
|
||||
return None
|
||||
return None
|
||||
|
||||
def program(self, p):
|
||||
if not p['pin_adm']:
|
||||
raise ValueError("Please provide a PIN-ADM as there is no default one")
|
||||
sw = self.verify_adm(h2b(p['pin_adm']))
|
||||
if sw != '9000':
|
||||
raise RuntimeError('Failed to authenticate with ADM key %s'%(p['pin_adm'],))
|
||||
|
||||
# EF.ICCID
|
||||
# TODO: Add programming of the ICCID
|
||||
if p.get('iccid'):
|
||||
print("Warning: Programming of the ICCID is not implemented for this type of card.")
|
||||
|
||||
# KI (Presumably a propritary file)
|
||||
# TODO: Add programming of KI
|
||||
if p.get('ki'):
|
||||
print("Warning: Programming of the KI is not implemented for this type of card.")
|
||||
|
||||
# OPc (Presumably a propritary file)
|
||||
# TODO: Add programming of OPc
|
||||
if p.get('opc'):
|
||||
print("Warning: Programming of the OPc is not implemented for this type of card.")
|
||||
|
||||
# EF.SMSP
|
||||
if p.get('smsp'):
|
||||
sw = self.update_smsp(p['smsp'])
|
||||
if sw != '9000':
|
||||
print("Programming SMSP failed with code %s"%sw)
|
||||
|
||||
# EF.IMSI
|
||||
if p.get('imsi'):
|
||||
sw = self.update_imsi(p['imsi'])
|
||||
if sw != '9000':
|
||||
print("Programming IMSI failed with code %s"%sw)
|
||||
|
||||
# EF.ACC
|
||||
if p.get('acc'):
|
||||
sw = self.update_acc(p['acc'])
|
||||
if sw != '9000':
|
||||
print("Programming ACC failed with code %s"%sw)
|
||||
|
||||
# EF.PLMNsel
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmnsel(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNsel failed with code %s"%sw)
|
||||
|
||||
# EF.PLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_plmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming PLMNwAcT failed with code %s"%sw)
|
||||
|
||||
# EF.OPLMNwAcT
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_oplmn_act(p['mcc'], p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming OPLMNwAcT failed with code %s"%sw)
|
||||
|
||||
# EF.AD
|
||||
if p.get('mcc') and p.get('mnc'):
|
||||
sw = self.update_ad(p['mnc'])
|
||||
if sw != '9000':
|
||||
print("Programming AD failed with code %s"%sw)
|
||||
|
||||
return None
|
||||
|
||||
def erase(self):
|
||||
return
|
||||
|
||||
|
||||
# In order for autodetection ...
|
||||
# In order for autodetection ...
|
||||
_cards_classes = [ FakeMagicSim, SuperSim, MagicSim, GrcardSim,
|
||||
SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1,
|
||||
FairwavesSIM, OpenCellsSim, WavemobileSim ]
|
||||
FairwavesSIM ]
|
||||
|
||||
def card_autodetect(scc):
|
||||
for kls in _cards_classes:
|
||||
|
||||
@@ -24,63 +24,13 @@
|
||||
|
||||
from pySim.utils import rpad, b2h
|
||||
|
||||
|
||||
class SimCardCommands(object):
|
||||
def __init__(self, transport):
|
||||
self._tp = transport;
|
||||
self._cla_byte = "a0"
|
||||
self.sel_ctrl = "0000"
|
||||
|
||||
# Get file size from FCP
|
||||
def __get_len_from_tlv(self, fcp):
|
||||
# see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF,
|
||||
# DF or ADF
|
||||
from pytlv.TLV import TLV
|
||||
tlvparser = TLV(['82', '83', '84', 'a5', '8a', '8b', '8c', '80', 'ab', 'c6', '81', '88'])
|
||||
|
||||
# pytlv is case sensitive!
|
||||
fcp = fcp.lower()
|
||||
|
||||
if fcp[0:2] != '62':
|
||||
raise ValueError('Tag of the FCP template does not match, expected 62 but got %s'%fcp[0:2])
|
||||
|
||||
# Unfortunately the spec is not very clear if the FCP length is
|
||||
# coded as one or two byte vale, so we have to try it out by
|
||||
# checking if the length of the remaining TLV string matches
|
||||
# what we get in the length field.
|
||||
# See also ETSI TS 102 221, chapter 11.1.1.3.0 Base coding.
|
||||
exp_tlv_len = int(fcp[2:4], 16)
|
||||
if len(fcp[4:])/2 == exp_tlv_len:
|
||||
skip = 4
|
||||
else:
|
||||
exp_tlv_len = int(fcp[2:6], 16)
|
||||
if len(fcp[4:])/2 == exp_tlv_len:
|
||||
skip = 6
|
||||
|
||||
# Skip FCP tag and length
|
||||
tlv = fcp[skip:]
|
||||
tlv_parsed = tlvparser.parse(tlv)
|
||||
|
||||
return int(tlv_parsed['80'], 16)
|
||||
|
||||
# Tell the length of a record by the card response
|
||||
# USIMs respond with an FCP template, which is different
|
||||
# from what SIMs responds. See also:
|
||||
# USIM: ETSI TS 102 221, chapter 11.1.1.3 Response Data
|
||||
# SIM: GSM 11.11, chapter 9.2.1 SELECT
|
||||
def __record_len(self, r):
|
||||
if self.sel_ctrl == "0004":
|
||||
return self.__get_len_from_tlv(r[-1])
|
||||
else:
|
||||
return int(r[-1][28:30], 16)
|
||||
|
||||
# Tell the length of a binary file. See also comment
|
||||
# above.
|
||||
def __len(self, r):
|
||||
if self.sel_ctrl == "0004":
|
||||
return self.__get_len_from_tlv(r[-1])
|
||||
else:
|
||||
return int(r[-1][4:8], 16)
|
||||
|
||||
def get_atr(self):
|
||||
return self._tp.get_atr()
|
||||
|
||||
@@ -105,16 +55,17 @@ class SimCardCommands(object):
|
||||
rv.append(data)
|
||||
return rv
|
||||
|
||||
def read_binary_selected(self, length, offset=0):
|
||||
pdu = self.cla_byte + 'b0%04x%02x' % (offset, (min(256, length) & 0xff))
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def read_binary(self, ef, length=None, offset=0):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
ef = [ef]
|
||||
r = self.select_file(ef)
|
||||
if len(r[-1]) == 0:
|
||||
return (None, None)
|
||||
if length is None:
|
||||
length = self.__len(r) - offset
|
||||
pdu = self.cla_byte + 'b0%04x%02x' % (offset, (min(256, length) & 0xff))
|
||||
return self._tp.send_apdu(pdu)
|
||||
length = int(r[-1][4:8], 16) - offset
|
||||
return self.read_binary_selected(length, offset)
|
||||
|
||||
def update_binary(self, ef, data, offset=0):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
@@ -123,20 +74,23 @@ class SimCardCommands(object):
|
||||
pdu = self.cla_byte + 'd6%04x%02x' % (offset, len(data)/2) + data
|
||||
return self._tp.send_apdu_checksw(pdu)
|
||||
|
||||
def read_record_selected(self, rec_length, rec_no):
|
||||
pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
|
||||
return self._tp.send_apdu(pdu)
|
||||
|
||||
def read_record(self, ef, rec_no):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
ef = [ef]
|
||||
r = self.select_file(ef)
|
||||
rec_length = self.__record_len(r)
|
||||
pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
|
||||
return self._tp.send_apdu(pdu)
|
||||
rec_length = int(r[-1][28:30], 16)
|
||||
return self.read_record_selected(rec_length, rec_no)
|
||||
|
||||
def update_record(self, ef, rec_no, data, force_len=False):
|
||||
if not hasattr(type(ef), '__iter__'):
|
||||
ef = [ef]
|
||||
r = self.select_file(ef)
|
||||
if not force_len:
|
||||
rec_length = self.__record_len(r)
|
||||
rec_length = int(r[-1][28:30], 16)
|
||||
if (len(data)/2 != rec_length):
|
||||
raise ValueError('Invalid data length (expected %d, got %d)' % (rec_length, len(data)/2))
|
||||
else:
|
||||
@@ -146,18 +100,32 @@ class SimCardCommands(object):
|
||||
|
||||
def record_size(self, ef):
|
||||
r = self.select_file(ef)
|
||||
return self.__record_len(r)
|
||||
return int(r[-1][28:30], 16)
|
||||
|
||||
def record_count(self, ef):
|
||||
r = self.select_file(ef)
|
||||
return self.__len(r) // self.__record_len(r)
|
||||
return int(r[-1][4:8], 16) // int(r[-1][28:30], 16)
|
||||
|
||||
def run_gsm(self, rand):
|
||||
def run_gsm_raw(self, rand):
|
||||
'''
|
||||
A3/A8 algorithm in the SIM card using the given RAND.
|
||||
This function returns a raw result tuple.
|
||||
'''
|
||||
if len(rand) != 32:
|
||||
raise ValueError('Invalid rand')
|
||||
self.select_file(['3f00', '7f20'])
|
||||
return self._tp.send_apdu(self.cla_byte + '88000010' + rand)
|
||||
|
||||
def run_gsm(self, rand):
|
||||
'''
|
||||
A3/A8 algorithm in the SIM card using the given RAND.
|
||||
This function returns a parsed ((SRES, Kc), sw) tuple.
|
||||
'''
|
||||
(res, sw) = self.run_gsm_raw(rand)
|
||||
if sw != '9000':
|
||||
return (res, sw)
|
||||
return ((res[0:8], res[8:]), sw)
|
||||
|
||||
def reset_card(self):
|
||||
return self._tp.reset_card()
|
||||
|
||||
|
||||
@@ -31,6 +31,3 @@ class NoCardError(exceptions.Exception):
|
||||
|
||||
class ProtocolError(exceptions.Exception):
|
||||
pass
|
||||
|
||||
class ReaderError(exceptions.Exception):
|
||||
pass
|
||||
|
||||
@@ -67,13 +67,7 @@ class LinkBase(object):
|
||||
"""
|
||||
data, sw = self.send_apdu_raw(pdu)
|
||||
|
||||
# When whe have sent the first APDU, the SW may indicate that there are response bytes
|
||||
# available. There are two SWs commonly used for this 9fxx (sim) and 61xx (usim), where
|
||||
# xx is the number of response bytes available.
|
||||
# See also:
|
||||
# SW1=9F: 3GPP TS 51.011 9.4.1, Responses to commands which are correctly executed
|
||||
# SW1=61: ISO/IEC 7816-4, Table 5 — General meaning of the interindustry values of SW1-SW2
|
||||
if (sw is not None) and ((sw[0:2] == '9f') or (sw[0:2] == '61')):
|
||||
if (sw is not None) and (sw[0:2] == '9f'):
|
||||
pdu_gr = pdu[0:2] + 'c00000' + sw[2:4]
|
||||
data, sw = self.send_apdu_raw(pdu_gr)
|
||||
|
||||
@@ -83,23 +77,12 @@ class LinkBase(object):
|
||||
"""send_apdu_checksw(pdu,sw): Sends an APDU and check returned SW
|
||||
|
||||
pdu : string of hexadecimal characters (ex. "A0A40000023F00")
|
||||
sw : string of 4 hexadecimal characters (ex. "9000"). The
|
||||
user may mask out certain digits using a '?' to add some
|
||||
ambiguity if needed.
|
||||
sw : string of 4 hexadecimal characters (ex. "9000")
|
||||
return : tuple(data, sw), where
|
||||
data : string (in hex) of returned data (ex. "074F4EFFFF")
|
||||
sw : string (in hex) of status word (ex. "9000")
|
||||
"""
|
||||
rv = self.send_apdu(pdu)
|
||||
|
||||
# Create a masked version of the returned status word
|
||||
sw_masked = ""
|
||||
for i in range(0, 4):
|
||||
if sw.lower()[i] == '?':
|
||||
sw_masked = sw_masked + '?'
|
||||
else:
|
||||
sw_masked = sw_masked + rv[1][i].lower()
|
||||
|
||||
if sw.lower() != sw_masked:
|
||||
raise RuntimeError("SW match failed! Expected %s and got %s." % (sw.lower(), rv[1]))
|
||||
if sw.lower() != rv[1]:
|
||||
raise RuntimeError("SW match failed ! Expected %s and got %s." % (sw.lower(), rv[1]))
|
||||
return rv
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
""" pySim: Transport Link for Calypso bases phones
|
||||
"""
|
||||
|
||||
#
|
||||
# Copyright (C) 2018 Vadim Yanitskiy <axilirator@gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import select
|
||||
import struct
|
||||
import socket
|
||||
import os
|
||||
|
||||
from pySim.transport import LinkBase
|
||||
from pySim.exceptions import *
|
||||
from pySim.utils import h2b, b2h
|
||||
|
||||
class L1CTLMessage(object):
|
||||
|
||||
# Every (encoded) L1CTL message has the following structure:
|
||||
# - msg_length (2 bytes, net order)
|
||||
# - l1ctl_hdr (packed structure)
|
||||
# - msg_type
|
||||
# - flags
|
||||
# - padding (2 spare bytes)
|
||||
# - ... payload ...
|
||||
|
||||
def __init__(self, msg_type, flags = 0x00):
|
||||
# Init L1CTL message header
|
||||
self.data = struct.pack("BBxx", msg_type, flags)
|
||||
|
||||
def gen_msg(self):
|
||||
return struct.pack("!H", len(self.data)) + self.data
|
||||
|
||||
class L1CTLMessageReset(L1CTLMessage):
|
||||
|
||||
# L1CTL message types
|
||||
L1CTL_RESET_REQ = 0x0d
|
||||
L1CTL_RESET_IND = 0x07
|
||||
L1CTL_RESET_CONF = 0x0e
|
||||
|
||||
# Reset types
|
||||
L1CTL_RES_T_BOOT = 0x00
|
||||
L1CTL_RES_T_FULL = 0x01
|
||||
L1CTL_RES_T_SCHED = 0x02
|
||||
|
||||
def __init__(self, type = L1CTL_RES_T_FULL):
|
||||
super(L1CTLMessageReset, self).__init__(self.L1CTL_RESET_REQ)
|
||||
self.data += struct.pack("Bxxx", type)
|
||||
|
||||
class L1CTLMessageSIM(L1CTLMessage):
|
||||
|
||||
# SIM related message types
|
||||
L1CTL_SIM_REQ = 0x16
|
||||
L1CTL_SIM_CONF = 0x17
|
||||
|
||||
def __init__(self, pdu):
|
||||
super(L1CTLMessageSIM, self).__init__(self.L1CTL_SIM_REQ)
|
||||
self.data += pdu
|
||||
|
||||
class CalypsoSimLink(LinkBase):
|
||||
|
||||
def __init__(self, sock_path = "/tmp/osmocom_l2"):
|
||||
# Make sure that a given socket path exists
|
||||
if not os.path.exists(sock_path):
|
||||
raise ReaderError("There is no such ('%s') UNIX socket" % sock_path)
|
||||
|
||||
print("Connecting to osmocon at '%s'..." % sock_path)
|
||||
|
||||
# Establish a client connection
|
||||
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.sock.connect(sock_path)
|
||||
|
||||
def __del__(self):
|
||||
self.sock.close()
|
||||
|
||||
def wait_for_rsp(self, exp_len = 128):
|
||||
# Wait for incoming data (timeout is 3 seconds)
|
||||
s, _, _ = select.select([self.sock], [], [], 3.0)
|
||||
if not s:
|
||||
raise ReaderError("Timeout waiting for card response")
|
||||
|
||||
# Receive expected amount of bytes from osmocon
|
||||
rsp = self.sock.recv(exp_len)
|
||||
return rsp
|
||||
|
||||
def reset_card(self):
|
||||
# Request FULL reset
|
||||
req_msg = L1CTLMessageReset()
|
||||
self.sock.send(req_msg.gen_msg())
|
||||
|
||||
# Wait for confirmation
|
||||
rsp = self.wait_for_rsp()
|
||||
rsp_msg = struct.unpack_from("!HB", rsp)
|
||||
if rsp_msg[1] != L1CTLMessageReset.L1CTL_RESET_CONF:
|
||||
raise ReaderError("Failed to reset Calypso PHY")
|
||||
|
||||
def connect(self):
|
||||
self.reset_card()
|
||||
|
||||
def disconnect(self):
|
||||
pass # Nothing to do really ...
|
||||
|
||||
def wait_for_card(self, timeout = None, newcardonly = False):
|
||||
pass # Nothing to do really ...
|
||||
|
||||
def send_apdu_raw(self, pdu):
|
||||
"""see LinkBase.send_apdu_raw"""
|
||||
|
||||
# Request FULL reset
|
||||
req_msg = L1CTLMessageSIM(h2b(pdu))
|
||||
self.sock.send(req_msg.gen_msg())
|
||||
|
||||
# Read message length first
|
||||
rsp = self.wait_for_rsp(struct.calcsize("!H"))
|
||||
msg_len = struct.unpack_from("!H", rsp)[0]
|
||||
if msg_len < struct.calcsize("BBxx"):
|
||||
raise ReaderError("Missing L1CTL header for L1CTL_SIM_CONF")
|
||||
|
||||
# Read the whole message then
|
||||
rsp = self.sock.recv(msg_len)
|
||||
|
||||
# Verify L1CTL header
|
||||
hdr = struct.unpack_from("BBxx", rsp)
|
||||
if hdr[0] != L1CTLMessageSIM.L1CTL_SIM_CONF:
|
||||
raise ReaderError("Unexpected L1CTL message received")
|
||||
|
||||
# Verify the payload length
|
||||
offset = struct.calcsize("BBxx")
|
||||
if len(rsp) <= offset:
|
||||
raise ProtocolError("Empty response from SIM?!?")
|
||||
|
||||
# Omit L1CTL header
|
||||
rsp = rsp[offset:]
|
||||
|
||||
# Unpack data and SW
|
||||
data = rsp[:-2]
|
||||
sw = rsp[-2:]
|
||||
|
||||
return b2h(data), b2h(sw)
|
||||
@@ -143,19 +143,19 @@ class SerialSimLink(LinkBase):
|
||||
for i in range(4):
|
||||
if t0 & (0x10 << i):
|
||||
b = self._rx_byte()
|
||||
self._atr.append(ord(b))
|
||||
self._atr.apend(ord(b))
|
||||
self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b)))
|
||||
|
||||
for i in range(0, t0 & 0xf):
|
||||
b = self._rx_byte()
|
||||
self._atr.append(ord(b))
|
||||
self._atr.apend(ord(b))
|
||||
self._dbg_print("Historical = %x" % ord(b))
|
||||
|
||||
while True:
|
||||
x = self._rx_byte()
|
||||
if not x:
|
||||
break
|
||||
self._atr.append(ord(x))
|
||||
self._atr.apend(ord(x))
|
||||
self._dbg_print("Extra: %x" % ord(x))
|
||||
|
||||
return 1
|
||||
|
||||
@@ -229,7 +229,7 @@ EF = {
|
||||
'SUME': DF['GSM']+[EF_num['SUME']],
|
||||
'PLMNwAcT': DF['GSM']+[EF_num['PLMNwAcT']],
|
||||
'OPLMNwAcT': DF['GSM']+[EF_num['OPLMNwAcT']],
|
||||
# Figure 8 names it HPLMNAcT, but in the text it's names it HPLMNwAcT
|
||||
# Figure 8 names it HPLMNAcT, but in the text it's named HPLMNwAcT
|
||||
'HPLMNAcT': DF['GSM']+[EF_num['HPLMNAcT']],
|
||||
'HPLMNwAcT': DF['GSM']+[EF_num['HPLMNAcT']],
|
||||
'CPBCCH': DF['GSM']+[EF_num['CPBCCH']],
|
||||
|
||||
126
pySim/utils.py
126
pySim/utils.py
@@ -49,29 +49,11 @@ def rpad(s, l, c='f'):
|
||||
def lpad(s, l, c='f'):
|
||||
return c * (l - len(s)) + s
|
||||
|
||||
def half_round_up(n):
|
||||
return (n + 1)//2
|
||||
|
||||
# IMSI encoded format:
|
||||
# For IMSI 0123456789ABCDE:
|
||||
#
|
||||
# | byte 1 | 2 upper | 2 lower | 3 upper | 3 lower | ... | 9 upper | 9 lower |
|
||||
# | length in bytes | 0 | odd/even | 2 | 1 | ... | E | D |
|
||||
#
|
||||
# If the IMSI is less than 15 characters, it should be padded with 'f' from the end.
|
||||
#
|
||||
# The length is the total number of bytes used to encoded the IMSI. This includes the odd/even
|
||||
# parity bit. E.g. an IMSI of length 14 is 8 bytes long, not 7, as it uses bytes 2 to 9 to
|
||||
# encode itself.
|
||||
#
|
||||
# Because of this, an odd length IMSI fits exactly into len(imsi) + 1 // 2 bytes, whereas an
|
||||
# even length IMSI only uses half of the last byte.
|
||||
|
||||
def enc_imsi(imsi):
|
||||
"""Converts a string imsi into the value of the EF"""
|
||||
l = half_round_up(len(imsi) + 1) # Required bytes - include space for odd/even indicator
|
||||
l = (len(imsi) + 1) // 2 # Required bytes
|
||||
oe = len(imsi) & 1 # Odd (1) / Even (0)
|
||||
ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
|
||||
ei = '%02x' % l + swap_nibbles(lpad('%01x%s' % ((oe<<3)|1, imsi), 16))
|
||||
return ei
|
||||
|
||||
def dec_imsi(ef):
|
||||
@@ -79,15 +61,13 @@ def dec_imsi(ef):
|
||||
if len(ef) < 4:
|
||||
return None
|
||||
l = int(ef[0:2], 16) * 2 # Length of the IMSI string
|
||||
l = l - 1 # Encoded length byte includes oe nibble
|
||||
swapped = swap_nibbles(ef[2:]).rstrip('f')
|
||||
swapped = swap_nibbles(ef[2:])
|
||||
oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
|
||||
if not oe:
|
||||
# if even, only half of last byte was used
|
||||
if oe:
|
||||
l = l-1
|
||||
if l != len(swapped) - 1:
|
||||
if l+1 > len(swapped):
|
||||
return None
|
||||
imsi = swapped[1:]
|
||||
imsi = swapped[1:l+2]
|
||||
return imsi
|
||||
|
||||
def dec_iccid(ef):
|
||||
@@ -98,7 +78,7 @@ def enc_iccid(iccid):
|
||||
|
||||
def enc_plmn(mcc, mnc):
|
||||
"""Converts integer MCC/MNC into 3 bytes for EF"""
|
||||
return swap_nibbles(lpad('%d' % mcc, 3) + lpad('%d' % mnc, 3))
|
||||
return swap_nibbles(lpad('%03d' % mcc, 3) + lpad('%02d' % mnc, 3))
|
||||
|
||||
def dec_spn(ef):
|
||||
byte1 = int(ef[0:2])
|
||||
@@ -113,79 +93,6 @@ def enc_spn(name, hplmn_disp=False, oplmn_disp=False):
|
||||
if oplmn_disp: byte1 = byte1|0x02
|
||||
return i2h([byte1])+s2h(name)
|
||||
|
||||
def hexstr_to_fivebytearr(s):
|
||||
return [s[i:i+10] for i in range(0, len(s), 10) ]
|
||||
|
||||
# Accepts hex string representing three bytes
|
||||
def dec_mcc_from_plmn(plmn):
|
||||
ia = h2i(plmn)
|
||||
digit1 = ia[0] & 0x0F # 1st byte, LSB
|
||||
digit2 = (ia[0] & 0xF0) >> 4 # 1st byte, MSB
|
||||
digit3 = ia[1] & 0x0F # 2nd byte, LSB
|
||||
if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
|
||||
return 0xFFF # 4095
|
||||
mcc = digit1 * 100
|
||||
mcc += digit2 * 10
|
||||
mcc += digit3
|
||||
return mcc
|
||||
|
||||
def dec_mnc_from_plmn(plmn):
|
||||
ia = h2i(plmn)
|
||||
digit1 = ia[2] & 0x0F # 3rd byte, LSB
|
||||
digit2 = (ia[2] & 0xF0) >> 4 # 3rd byte, MSB
|
||||
digit3 = (ia[1] & 0xF0) >> 4 # 2nd byte, MSB
|
||||
if digit3 == 0xF and digit2 == 0xF and digit1 == 0xF:
|
||||
return 0xFFF # 4095
|
||||
mnc = 0
|
||||
# signifies two digit MNC
|
||||
if digit3 == 0xF:
|
||||
mnc += digit1 * 10
|
||||
mnc += digit2
|
||||
else:
|
||||
mnc += digit1 * 100
|
||||
mnc += digit2 * 10
|
||||
mnc += digit3
|
||||
return mnc
|
||||
|
||||
def dec_act(twohexbytes):
|
||||
act_list = [
|
||||
{'bit': 15, 'name': "UTRAN"},
|
||||
{'bit': 14, 'name': "E-UTRAN"},
|
||||
{'bit': 7, 'name': "GSM"},
|
||||
{'bit': 6, 'name': "GSM COMPACT"},
|
||||
{'bit': 5, 'name': "cdma2000 HRPD"},
|
||||
{'bit': 4, 'name': "cdma2000 1xRTT"},
|
||||
]
|
||||
ia = h2i(twohexbytes)
|
||||
u16t = (ia[0] << 8)|ia[1]
|
||||
sel = []
|
||||
for a in act_list:
|
||||
if u16t & (1 << a['bit']):
|
||||
sel.append(a['name'])
|
||||
return sel
|
||||
|
||||
def dec_xplmn_w_act(fivehexbytes):
|
||||
res = {'mcc': 0, 'mnc': 0, 'act': []}
|
||||
plmn_chars = 6
|
||||
act_chars = 4
|
||||
plmn_str = fivehexbytes[:plmn_chars] # first three bytes (six ascii hex chars)
|
||||
act_str = fivehexbytes[plmn_chars:plmn_chars + act_chars] # two bytes after first three bytes
|
||||
res['mcc'] = dec_mcc_from_plmn(plmn_str)
|
||||
res['mnc'] = dec_mnc_from_plmn(plmn_str)
|
||||
res['act'] = dec_act(act_str)
|
||||
return res
|
||||
|
||||
def format_xplmn_w_act(hexstr):
|
||||
s = ""
|
||||
for rec_data in hexstr_to_fivebytearr(hexstr):
|
||||
rec_info = dec_xplmn_w_act(rec_data)
|
||||
if rec_info['mcc'] == 0xFFF and rec_info['mnc'] == 0xFFF:
|
||||
rec_str = "unused"
|
||||
else:
|
||||
rec_str = "MCC: %3s MNC: %3s AcT: %s" % (rec_info['mcc'], rec_info['mnc'], ", ".join(rec_info['act']))
|
||||
s += "\t%s # %s\n" % (rec_data, rec_str)
|
||||
return s
|
||||
|
||||
def derive_milenage_opc(ki_hex, op_hex):
|
||||
"""
|
||||
Run the milenage algorithm to calculate OPC from Ki and OP
|
||||
@@ -206,3 +113,22 @@ def calculate_luhn(cc):
|
||||
num = map(int, str(cc))
|
||||
check_digit = 10 - sum(num[-2::-2] + [sum(divmod(d * 2, 10)) for d in num[::-2]]) % 10
|
||||
return 0 if check_digit == 10 else check_digit
|
||||
|
||||
def dec_select_ef_response(response):
|
||||
'''
|
||||
As defined in the TS 151.011 9.2.1 SELECT
|
||||
'''
|
||||
|
||||
length = int(response[4:8], 16)
|
||||
file_id = response[8:12]
|
||||
file_type = response[12:14]
|
||||
increase_cmd = response[14:16]
|
||||
access_cond = response[16:22]
|
||||
file_status = response[22:24]
|
||||
data_len = int(response[24:26], 16)
|
||||
ef_struct = response[26:28]
|
||||
if len(response) >= 30:
|
||||
record_len = int(response[28:30], 16)
|
||||
else:
|
||||
record_len = 0
|
||||
return (length, file_id, file_type, increase_cmd, access_cond, file_status, data_len, ef_struct, record_len)
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/pyton
|
||||
|
||||
import unittest
|
||||
import utils
|
||||
|
||||
class DecTestCase(unittest.TestCase):
|
||||
|
||||
def testSplitHexStringToListOf5ByteEntries(self):
|
||||
input_str = "ffffff0003ffffff0002ffffff0001"
|
||||
expected = [
|
||||
"ffffff0003",
|
||||
"ffffff0002",
|
||||
"ffffff0001",
|
||||
]
|
||||
self.assertEqual(utils.hexstr_to_fivebytearr(input_str), expected)
|
||||
|
||||
def testDecMCCfromPLMN(self):
|
||||
self.assertEqual(utils.dec_mcc_from_plmn("92f501"), 295)
|
||||
|
||||
def testDecMCCfromPLMN_unused(self):
|
||||
self.assertEqual(utils.dec_mcc_from_plmn("ff0f00"), 4095)
|
||||
|
||||
def testDecMNCfromPLMN_twoDigitMNC(self):
|
||||
self.assertEqual(utils.dec_mnc_from_plmn("92f501"), 10)
|
||||
|
||||
def testDecMNCfromPLMN_threeDigitMNC(self):
|
||||
self.assertEqual(utils.dec_mnc_from_plmn("031263"), 361)
|
||||
|
||||
def testDecMNCfromPLMN_unused(self):
|
||||
self.assertEqual(utils.dec_mnc_from_plmn("00f0ff"), 4095)
|
||||
|
||||
def testDecAct_noneSet(self):
|
||||
self.assertEqual(utils.dec_act("0000"), [])
|
||||
|
||||
def testDecAct_onlyUtran(self):
|
||||
self.assertEqual(utils.dec_act("8000"), ["UTRAN"])
|
||||
|
||||
def testDecAct_onlyEUtran(self):
|
||||
self.assertEqual(utils.dec_act("4000"), ["E-UTRAN"])
|
||||
|
||||
def testDecAct_onlyGsm(self):
|
||||
self.assertEqual(utils.dec_act("0080"), ["GSM"])
|
||||
|
||||
def testDecAct_onlyGsmCompact(self):
|
||||
self.assertEqual(utils.dec_act("0040"), ["GSM COMPACT"])
|
||||
|
||||
def testDecAct_onlyCdma2000HRPD(self):
|
||||
self.assertEqual(utils.dec_act("0020"), ["cdma2000 HRPD"])
|
||||
|
||||
def testDecAct_onlyCdma20001xRTT(self):
|
||||
self.assertEqual(utils.dec_act("0010"), ["cdma2000 1xRTT"])
|
||||
|
||||
def testDecAct_allSet(self):
|
||||
self.assertEqual(utils.dec_act("ffff"), ["UTRAN", "E-UTRAN", "GSM", "GSM COMPACT", "cdma2000 HRPD", "cdma2000 1xRTT"])
|
||||
|
||||
def testDecxPlmn_w_act(self):
|
||||
expected = {'mcc': 295, 'mnc': 10, 'act': ["UTRAN"]}
|
||||
self.assertEqual(utils.dec_xplmn_w_act("92f5018000"), expected)
|
||||
|
||||
def testFormatxPlmn_w_act(self):
|
||||
input_str = "92f501800092f5508000ffffff0000ffffff0000ffffff0000ffffff0000ffffff0000ffffff0000ffffff0000ffffff0000"
|
||||
expected = '''92f5018000 # MCC: 295 MNC: 10 AcT: UTRAN
|
||||
92f5508000 # MCC: 295 MNC: 5 AcT: UTRAN
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
'''
|
||||
self.assertEqual(utils.format_xplmn_w_act(input_str), expected)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -1,6 +0,0 @@
|
||||
MCC=001
|
||||
MNC=01
|
||||
ICCID=1122334455667788990
|
||||
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
IMSI=001010000000102
|
||||
@@ -1,14 +0,0 @@
|
||||
Using PC/SC reader (dev=1) interface
|
||||
Reading ...
|
||||
ICCID: 1122334455667788990
|
||||
IMSI: 001010000000102
|
||||
SMSP: ffffffffffffffffffffffffe1ffffffffffffffffffffffff0581005155f5ffffffffffff000000
|
||||
PLMNsel: fff11fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
PLMNwAcT: Can't read file -- SW match failed! Expected 9000 and got 9404.
|
||||
OPLMNwAcT: Can't read file -- SW match failed! Expected 9000 and got 9404.
|
||||
HPLMNAcT: Can't read file -- SW match failed! Expected 9000 and got 9404.
|
||||
ACC: ffff
|
||||
MSISDN: Not available
|
||||
AD: 000000
|
||||
Done !
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
MCC=001
|
||||
MNC=01
|
||||
ICCID=1122334455667788990
|
||||
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
IMSI=001010000000102
|
||||
ADM=55538407
|
||||
@@ -1,53 +0,0 @@
|
||||
Using PC/SC reader (dev=0) interface
|
||||
Reading ...
|
||||
ICCID: 1122334455667788990
|
||||
IMSI: 001010000000102
|
||||
SMSP: ffffffffffffffffffffffffffffffffffffffffffffffffe1ffffffffffffffffffffffff0581005155f5ffffffffffff000000
|
||||
PLMNsel: fff11fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
PLMNwAcT:
|
||||
fff11fffff # MCC: 1651 MNC: 151 AcT: UTRAN, E-UTRAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
|
||||
OPLMNwAcT:
|
||||
fff11fffff # MCC: 1651 MNC: 151 AcT: UTRAN, E-UTRAN, GSM, GSM COMPACT, cdma2000 HRPD, cdma2000 1xRTT
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
ffffff0000 # unused
|
||||
|
||||
HPLMNAcT:
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
ffffffffff # unused
|
||||
|
||||
ACC: 0008
|
||||
MSISDN: Not available
|
||||
AD: 00000002
|
||||
Done !
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
MCC=001
|
||||
MNC=01
|
||||
ICCID=1122334455667788990
|
||||
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
IMSI=001010000000102
|
||||
ADM=DDDDDDDD
|
||||
@@ -1,14 +0,0 @@
|
||||
Using PC/SC reader (dev=2) interface
|
||||
Reading ...
|
||||
ICCID: 1122334455667788990
|
||||
IMSI: 001010000000102
|
||||
SMSP: ffffffffffffffffffffffffe1ffffffffffffffffffffffff0581005155f5ffffffffffff000000
|
||||
PLMNsel: fff11fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
||||
PLMNwAcT: Can't read file -- SW match failed! Expected 9000 and got 9404.
|
||||
OPLMNwAcT: Can't read file -- SW match failed! Expected 9000 and got 9404.
|
||||
HPLMNAcT: Can't read file -- SW match failed! Expected 9000 and got 9404.
|
||||
ACC: 0008
|
||||
MSISDN: Not available
|
||||
AD: 000000
|
||||
Done !
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
MCC=001
|
||||
MNC=01
|
||||
IMSI=001010000000102
|
||||
ADM=0123456789ABCDEF
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
MCC=001
|
||||
MNC=01
|
||||
ICCID=1122334455667788990
|
||||
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
IMSI=001010000000102
|
||||
@@ -1,221 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Utility to verify the functionality of pysim-prog.py
|
||||
#
|
||||
# (C) 2018 by Sysmocom s.f.m.c. GmbH
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Author: Philipp Maier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
PYSIM_PROG=../pySim-prog.py
|
||||
PYSIM_READ=../pySim-read.py
|
||||
TEMPFILE=temp.tmp
|
||||
|
||||
set -e
|
||||
|
||||
echo "pysim-test - a test program to test pysim-prog.py"
|
||||
echo "================================================="
|
||||
|
||||
# Generate a list of the cards we expect to see by checking which .ok files
|
||||
# are present
|
||||
function gen_card_list {
|
||||
N_CARDS=0
|
||||
|
||||
echo "Expecting to see the following cards:"
|
||||
|
||||
for I in *.data ; do
|
||||
CARD_NAMES[$N_CARDS]=${I%.*}
|
||||
CARD_SEEN[$N_CARDS]=0
|
||||
N_CARDS=$((N_CARDS+1))
|
||||
done
|
||||
|
||||
for I in $(seq 0 $((N_CARDS-1))); do
|
||||
echo ${CARD_NAMES[$I]}
|
||||
done
|
||||
}
|
||||
|
||||
# Increment counter in card list for a specified card name (type)
|
||||
function inc_card_list {
|
||||
CARD_NAME=$1
|
||||
for I in $(seq 0 $((N_CARDS-1))); do
|
||||
if [ $CARD_NAME = ${CARD_NAMES[$I]} ]; then
|
||||
CARD_SEEN[$I]=$((${CARD_NAMES[$I]}+1))
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Check the card list, each card must be seen exactly one times
|
||||
function check_card_list {
|
||||
for I in $(seq 0 $((N_CARDS-1))); do
|
||||
if [ ${CARD_SEEN[$I]} -ne 1 ]; then
|
||||
echo "Error: Card ${CARD_NAMES[$I]} seen ${CARD_SEEN[$I]} times!"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "All cards seen -- everything ok!"
|
||||
}
|
||||
|
||||
# Verify the contents of a card by reading them and then diffing against the
|
||||
# previously created .ok file
|
||||
function check_card {
|
||||
TERMINAL=$1
|
||||
CARD_NAME=$2
|
||||
echo "Verifying card ..."
|
||||
stat ./$CARD_NAME.ok > /dev/null
|
||||
python $PYSIM_READ -p $TERMINAL > $TEMPFILE
|
||||
set +e
|
||||
CARD_DIFF=$(diff $TEMPFILE ./$CARD_NAME.ok)
|
||||
set -e
|
||||
|
||||
if [ "$CARD_DIFF" != "" ]; then
|
||||
echo "Card contents do not match the test data:"
|
||||
echo "Expected: $CARD_NAME.ok"
|
||||
echo "------------8<------------"
|
||||
cat "$CARD_NAME.ok"
|
||||
echo "------------8<------------"
|
||||
echo "Got:"
|
||||
echo "------------8<------------"
|
||||
cat $TEMPFILE
|
||||
echo "------------8<------------"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
inc_card_list $CARD_NAME
|
||||
|
||||
echo "Card contents match the test data -- success!"
|
||||
rm $TEMPFILE
|
||||
}
|
||||
|
||||
# Read out the card using pysim-read and store the result as .ok file. This
|
||||
# data will be used later in order to verify the results of our write tests.
|
||||
function gen_ok_file {
|
||||
TERMINAL=$1
|
||||
CARD_NAME=$2
|
||||
python $PYSIM_READ -p $TERMINAL > "$CARD_NAME.ok"
|
||||
echo "Generated file: $CARD_NAME.ok"
|
||||
echo "------------8<------------"
|
||||
cat "$CARD_NAME.ok"
|
||||
echo "------------8<------------"
|
||||
}
|
||||
|
||||
# Find out the type (card name) of the card that is installed in the specified
|
||||
# reader
|
||||
function probe_card {
|
||||
TERMINAL=$1
|
||||
RESULT=$(timeout 5 $PYSIM_PROG -p $TERMINAL -T | cut -d ":" -f 2 | tail -n 1 | xargs)
|
||||
echo $RESULT
|
||||
}
|
||||
|
||||
# Read out all cards and store the results as .ok files
|
||||
function gen_ok_files {
|
||||
echo "== OK FILE GENERATION =="
|
||||
for I in $(seq 0 $((N_TERMINALS-1))); do
|
||||
echo "Probing card in terminal #$I"
|
||||
CARD_NAME=$(probe_card $I)
|
||||
if [ -z "$CARD_NAME" ]; then
|
||||
echo "Error: Unresponsive card!"
|
||||
exit 1
|
||||
fi
|
||||
echo "Card is of type: $CARD_NAME"
|
||||
gen_ok_file $I $CARD_NAME
|
||||
done
|
||||
}
|
||||
|
||||
# Execute tests. Each card is programmed and the contents are checked
|
||||
# afterwards.
|
||||
function run_test {
|
||||
for I in $(seq 0 $((N_TERMINALS-1))); do
|
||||
echo "== EXECUTING TEST =="
|
||||
echo "Probing card in terminal #$I"
|
||||
CARD_NAME=$(probe_card $I)
|
||||
if [ -z "$CARD_NAME" ]; then
|
||||
echo "Error: Unresponsive card!"
|
||||
exit 1
|
||||
fi
|
||||
echo "Card is of type: $CARD_NAME"
|
||||
|
||||
# Make sure some default data is set
|
||||
MCC=001
|
||||
MNC=01
|
||||
ICCID=1122334455667788990
|
||||
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
IMSI=001010000000001
|
||||
ADM=00000000
|
||||
|
||||
. "$CARD_NAME.data"
|
||||
python $PYSIM_PROG -p $I -t $CARD_NAME -o $OPC -k $KI -x $MCC -y $MNC -i $IMSI -s $ICCID -a $ADM
|
||||
check_card $I $CARD_NAME
|
||||
echo ""
|
||||
done
|
||||
}
|
||||
|
||||
function usage {
|
||||
echo "Options:"
|
||||
echo "-n: number of card terminals"
|
||||
echo "-o: generate .ok files"
|
||||
}
|
||||
|
||||
# Make sure that the pathes to the python scripts always work, regardless from
|
||||
# where the script is called.
|
||||
CURDIR=$PWD
|
||||
SCRIPTDIR=$(dirname $0)
|
||||
cd $SCRIPTDIR
|
||||
PYSIM_PROG=$(realpath $PYSIM_PROG)
|
||||
PYSIM_READ=$(realpath $PYSIM_READ)
|
||||
cd $CURDIR
|
||||
|
||||
OPT_N_TERMINALS=0
|
||||
OPT_GEN_OK_FILES=0
|
||||
while getopts ":hon:" OPT; do
|
||||
case $OPT in
|
||||
h)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
o)
|
||||
OPT_GEN_OK_FILES=1
|
||||
;;
|
||||
n)
|
||||
OPT_N_TERMINALS=$OPTARG
|
||||
;;
|
||||
\?)
|
||||
echo "Invalid option: -$OPTARG" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
N_TERMINALS=$OPT_N_TERMINALS
|
||||
|
||||
# Generate a list of available cards, if no explicit reader number is given
|
||||
# then the number of cards will be used as reader number.
|
||||
gen_card_list
|
||||
if [ $N_TERMINALS -eq 0 ]; then
|
||||
N_TERMINALS=$N_CARDS
|
||||
fi
|
||||
echo "Number of card terminals installed: $N_TERMINALS"
|
||||
echo ""
|
||||
|
||||
if [ $OPT_GEN_OK_FILES -eq 1 ]; then
|
||||
gen_ok_files
|
||||
exit 0
|
||||
else
|
||||
run_test
|
||||
check_card_list
|
||||
exit 0
|
||||
fi
|
||||
@@ -1,7 +0,0 @@
|
||||
MCC=001
|
||||
MNC=01
|
||||
ICCID=1122334455667788990
|
||||
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
IMSI=001010000000102
|
||||
ADM=12345678
|
||||
@@ -1,7 +0,0 @@
|
||||
MCC=001
|
||||
MNC=01
|
||||
ICCID=1122334455667788990
|
||||
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
|
||||
IMSI=001010000000102
|
||||
ADM=DDDDDDDD
|
||||
Reference in New Issue
Block a user