96 Commits

Author SHA1 Message Date
Daniel Willmann
1d96d22c11 Add an option to read the iccid and batch-program the new card data
This can be used to reprogram everything including IMSI on the card:

while true; do
 ./pySim-prog.py -p 0 -t sysmoUSIM-SJS1 --source=csv --read-csv=cards.csv --read-iccid &&
   paplay complete.oga
 sleep 2
done

Change-Id: Ib343a29141b5255f67a59ab76959b51e162b7916
2019-08-28 23:22:28 +02:00
Daniel Willmann
1d00c19d14 pySim-prog: Use CSV format with headers
This way we can have optional fields like adm1 in the file
Also require iccid as identifier for the SIM card

Change-Id: I0d317ea51d0cf582b82157eec6cdec074001a236
2019-08-28 23:21:56 +02:00
Harald Welte
23888dab85 make writing SMSP optional
Change-Id: Ic5fdd397244cfe73b5b6a12883316072cc10f7b4
2019-08-28 23:20:02 +02:00
Daniel Willmann
67acdbc817 Make programming OPC optional
Change-Id: Ic600c325557918cb7d5b1fb179c01936a078c96c
2019-08-28 23:19:57 +02:00
Vadim Yanitskiy
35a96edb0d pySim-*.py: print info about selected reader interface
Change-Id: Idd791d7ef635e15915aab13274aefc15e70777b0
2019-05-10 22:24:21 +07:00
Max
5491c48e71 Don't try to parse result if select_file() failed
Change-Id: I25b859374e33654e58d07061926bf8529eab87f3
2019-05-10 17:03:58 +02:00
Max
89cfded971 Use reference array for reading ICCID
Change-Id: Iad0a328c6f0c9a4ab678efe068801849be48d8fc
2019-05-10 17:03:58 +02:00
Daniel Laszlo Sitzer
851e9c0f4c utils: add EF [H|O]PLMNwAcT decoding.
Allow decoding and pretty printing of PLMNwAcT, HPLMNwAcT and OPLMNwAct.

Includes unit tests for the added functions.

Change-Id: I9b8ca6ffd98f665690b84239d9a228e2c72c6ff9
2019-05-10 17:03:58 +02:00
Harald Welte
91d4ec7e7b import pysim-testdata of automatic testing to this repository
This test data is used by the jenkins build verification for pySim,
and it was previously located locally on the build slave.

By moving the testdata to this repository, any contributor can
modify both the code and the expected test results simultaneously.

Change-Id: I6714b091a114035d6aab8ba750c5f2b86e438467
2019-05-10 17:03:58 +02:00
Philipp Maier
6e507a7786 wavemobile-sim: write mnc-length field ine EF.AD
The length field in wavemobile sim cards is not set, so that the field
stays at its initial value, which is 0xFF. Lets write the correct mnc
length here.

Change-Id: Ieda0ce864bf3e8c7b92f062eaa3a5482c98507e2
Related: OS#3850
2019-04-01 16:33:48 +02:00
Philipp Maier
45daa925bd cosmetic: fix sourecode formatting
Change-Id: I4836826811ffb0aeb103d32eb874f5fa52af4186
2019-04-01 15:50:50 +02:00
Philipp Maier
ee908ae195 sysmo-usim-sjs1: update EF.AD with correct MNC length
At the moment EF.AD, which contains the length of the MNC is not
updated. For two digit MNC (the usual case) this is fine since the
length is set to 2 by default. However, when one wants to set an MNC
with 3 digit length the file must be updated, otherwise the third digit
of the MNC is recognized as part of the MSIN.

Change-Id: I827092b2c7f7952f54b2d9f8dbda419a0dbfaf65
Related: OS#3850
2019-04-01 15:50:50 +02:00
Philipp Maier
2d15ea015e cards: sysmo-usim-sjs1: add programming of EF.PLMNsel, EF.PLMNwAcT and EF.OPLMNwAcT
The files EF.PLMNsel, EF.PLMNwAcT and EF.OPLMNwAcT are currently not
programmed for sysmo-usim-sjs1, lets add them.

Change-Id: I0cac3041f1902383d98d6dc211cf31ae6e3a610b
Related: OS#3850
2019-03-21 18:04:26 +01:00
Philipp Maier
91f26d7f0a commands: correct case of a TLV tag (A5 => a5)
The hexadecimal tag defintions of pytlv are case sensitive strings. So
'A5' is something different than 'a5'. Pytlv uses lower case letters for
the upper hexadecimal digits. Lets correct this.

Change-Id: I41a9933707783f6b1b68ebd91a365405ac0892d0
Related: OS#3850
2019-03-21 17:24:18 +01:00
Vadim Yanitskiy
9f9f5a6157 pySim-*.py: add command line option for Calypso reader
Change-Id: Ia895ced62d29e06ae8af05cd95c9d181fb53b9df
2018-10-29 02:45:54 +07:00
Vadim Yanitskiy
588f3aca3c pySim-*.py: refactor card reader driver initialization
This would facilitate adding new card reader drivers.

Change-Id: Ia893537786c95a6aab3a51fb1ba7169023d5ef97
2018-10-29 01:58:18 +07:00
Vadim Yanitskiy
1381ff15af pySim/transport: introduce Calypso based reader interface
This interface allows to use a Calypso based phone (e.g. Motorola
C1XX) as a SIM card reader. It basically implements a few L1CTL
messages that are used to interact with the SIM card through
the OsmocomBB 'layer1' firmware.

Please note, that this is an experimental implementation, and
there is a risk that SIM programming would fail. Nevertheless,
I've managed to program and read one of my SIMs a few times.

Change-Id: Iec8101140581bf9e2cf7cf3a0b54bdf1875fc51b
2018-10-29 01:58:11 +07:00
Daniel Willmann
4fa8f1c49b pySim-prog: Honor international '+' in SMSC number
The smsc no. programmed by pySim-prog would always be a national number
in the past. Check whether the first 'digit' is a + and indicate that it
is an international number.

Change-Id: Ia79913f5b0307e9786a5acea75c0811927be2eef
2018-10-05 13:19:17 +02:00
Ben Fox-Moore
0ec147513c utils: fix encoding/decoding of IMSI value
When programming or reading a SIM with an IMSI shorter than 15, the IMSI
value is incorrectly encoded/decoded.

The code pads the the IMSI value with 0xF from the left but padding from
the right would be correct.

It also encodes the length as half the number of digits in the IMSI
(rounded up). This isn't correct for even length IMSIs. With even length
IMSIs, the odd/even parity bit bumps the last digit into an extra byte,
which should be counted as well.

- Fix endcoding of IMSI value
- Fix decoding of IMSI value

Change-Id: I9ae4ca4eb7c2965e601a7108843d052ff613beb9
Patch-by: Ben Foxmoore
Closes: SYS#3552
2018-09-26 18:14:19 +02:00
Philipp Maier
c555e18ebb tests: add example test data for Wavemobile-SIM
Change-Id: If69cd2d8af6d544155b3088f77eb6ea4b789901d
Related: SYS#4245
2018-09-13 09:43:26 +02:00
Philipp Maier
c8ce82a11f cards: Add support for Wavemobile SIM
Add support to handle Wavemobile sim cards. The support excludes some
parameters. For example it is not possible to write own KI keys yet.

Support covers the following files:
EF.SMSP
EF.IMSI
EF.ACC
EF.PLMNsel
EF.PLMNwAcT
EF.OPLMNwAcT

Not yet supported are:
EF.ICCID
KI (propretary file)
OPc (propritary file)

Change-Id: Ida3f37bd6e3ac995812aeddc9770f1ccd54ecf3f
Related: SYS#4245
2018-09-13 07:18:33 +00:00
Philipp Maier
11a2e3ca10 tests: add example file for sysmosim-gr1
Change-Id: I36cfa81f9029bfaedd2e802a2d709596dbeb20ab
Related: OS#3405
2018-08-23 10:44:51 +02:00
Philipp Maier
a3de5a331e cards: do not feed ascii as adm for sysmosim-gr1
When sysmosom-gr1 is used with a custom ADM key, then the ADM string is
not fed through h2b() like we see it with sysmo-usim-sjs1 for example.

- feed the ADM to h2b() before use

Change-Id: I0b7cab380b89612ed3b8318e014161038335fe1b
Related: OS#3405
2018-08-23 10:44:33 +02:00
Philipp Maier
087feff7cb cards: autodetect sysmosim-gr1
The sysmosim-gr1 lacks the auto detection feature because no autodetect
is yet programmed inside the related class.

- add autodetect for sysmosim-gr1

Change-Id: Iec1f1ab6824ff2328baedd731b08df997df4da01
Related: OS#3405
2018-08-23 10:43:57 +02:00
Neels Hofmeyr
52f59aca78 readme: add 'serial' dep, cosmetically rearrange
Change-Id: I414b897ba4911b7428ddf807cb04a93700ee0193
2018-08-20 21:54:34 +02:00
Philipp Maier
fb98dd6d6e commands: depend on pytlv only when it is actually needed
Some of the USIM-Card programming implementations do not need to look
at card responses, which means they also do not have to parse TLV
data. Lets depend on pytlv only in cases where TLV data has to be
parsed so that useser of cards that do not need at can go without
installing pytlv.

Change-Id: Ida841d74d9581e7f395751b0f74556a06a038de6
2018-07-25 18:52:58 +00:00
Philipp Maier
53b6dc216f readme: add info about dependencies
So far, the README.md file does not mention much about the
dependencies. This commt adds at lest the most important ones

Change-Id: I912bbf787e1408100183cade2113bf7617b86ffa
2018-07-25 18:52:58 +00:00
Alexander Couzens
73a0f047a1 add .gitreview
git review uses this file to build the git url.

Change-Id: Ia35aeb33d43ace3c71689d5acbb8d1259ccb7b42
2018-07-19 23:51:00 +02:00
Alexander Couzens
4731232aae contrib: add jenkins.sh
Run the tests.

Change-Id: I9308f097bf77d82e5ef78d1be551baf127726f74
2018-07-19 23:46:38 +02:00
Philipp Maier
7f340851fc tests: add test program to verify pysim-prog.py (and pysim-read.py)
Pysim now supports quite a number of different cards. Estimating
if changes in pysim introce regressions becomes increasingly difficult

The script that is added with this patch is intended to run as
atomated testsuit on real cards attached to a test system. However,
it can also be used by developers locally to check for regressions.

Change-Id: I8c6f95998272333bc757b34e3ab6be004e8cd674
Related: OS#3376
2018-07-19 07:55:42 +00:00
Philipp Maier
af9ae8bd6e cards: return SW in method update_plmnsel() of class Card
The method update_plmnsel() does not return the status word yet, which
causes pysim-prog.py to print an error message that does not influence
the functionality but does not look nice.

- preserve the status word that is returned with update_binary() and
  return it properly like the other methods do.

Change-Id: I54e8e165f87365e8162b36d24efc8f0db62b66da
Related: SYS#4245
2018-07-13 11:38:04 +02:00
Philipp Maier
1be35bfa09 cosmetic: fix cut and paste error in comment
Change-Id: I506585f8b300bb7a3e20edad7eca134374581d76
2018-07-13 11:36:26 +02:00
Philipp Maier
a2650496ce pySim-read: read contents of PLMN related files.
The files EF.PLMNsel, EF.PLMNwAcT, EF.OPLMNwAcT, EF.HPLMNAcT are not
yet printed by pysim-read. Lets add support for those files.

Change-Id: Ice802033adfa6fc1bccc76da47495eb29c3aef6c
Related: SYS#4245
2018-07-13 08:57:20 +00:00
Philipp Maier
ea6bdf0b99 pySim-read: Print exception when reading of EF.MSISDN fails
At the moment the exception is catched, but there is only a vague
error message printed. However, the exception string usually tells
us the status word, so it is very valuable to see it. Lets make
sure that the exception string is printed here.

Change-Id: Icb30470b1c0eee6a15fc028da820e92bf9ded27a
2018-07-13 08:56:58 +00:00
Philipp Maier
589c1a4ff5 cosmetic: fix excess space in log output (typo)
There shouldn't be a space between the end of a sentence and a
punctuation mark.

Change-Id: I33f9b8c803ccd185ffa370d235363423d82ba9c7
2018-07-13 08:50:33 +00:00
Philipp Maier
5bf42600d4 cards: add update method for EF:PLMNsel to Card class
The Card class offers update methods for various EF, but for
PLMNsel there is no update method available yet. Lets add one.

Change-Id: I832f7bef70c92dc101b94ad871b6cafaa626e134
Related: SYS#4245
2018-07-11 23:25:58 +02:00
Philipp Maier
ac9dde683f pysim-prog: add commandline option to probe cards
In some situations it may be helpful to know the card name (type)
we deal with in advance. So lets ad an to probe that only detects
the card and then exists.

- Add commandline option -T --probe

Change-Id: I57422d3819d52fd215ac8f13f890729aad2af76f
Related: OS#3376
2018-07-04 11:54:01 +00:00
Philipp Maier
0e3fcaa1bb commands: get file/record length from FCP (USIM)
Some of the methods SimCardCommands() have ways to determaine length
information from the card response. Regular sims use a format where
the length field is on a fixed position. USIMs use FCP templates
(format control parameters), which is a TLV encodecs string. So lets
distingish if we deal with an USIM (We can easily do this by looking
at the select control parameters) and extract the length info from
the FCP.

- If we deal with USIMs, use the FCP to determine length
  information

Change-Id: I068cf8a532e1c79a2d208e9d275c155ddb72713c
Related: SYS#4245
2018-07-03 15:17:16 +02:00
Philipp Maier
859e0fd1ce __init__: also fetch response bytes for USIMs automatically
The method send_apdu() first transmits the APDU in the cards direction.
The card may indicate that there is a response available by responding
with SW1=9F, where SW2 is the number of bytes. send_apdu() will then
craft a get-response APDU to pickup the response bytes. This mechanism
works fine for SIM, but USIM uses SW1=61 to indicate the availability
of a response, so lets also sense on SW1=61 to support USIMs as well.

- Also check on SW1=61 to see if a response is available

Change-Id: Ied7fb78873a7c4109de471c7a5e9c3701ba0c7d5
Related: SYS#4245
2018-07-03 13:17:04 +00:00
Philipp Maier
f779231da2 csv: fix missing import (broken CSV support)
with Change I38f5d36d16b41b5d516a6a3e2ec1d09637883932, new constants
for file identifiers were introduced. When csv file input is used,
then pySim-prog.py uses one of these constans without importing
it from ts_51_011.py

- Add missing import

Change-Id: Ic5b067b16ec204c2ba2264b1ffb48d37be8d5eb3
2018-07-03 12:53:37 +00:00
Philipp Maier
d4ebb6fdf4 __init__: allow wildcards in expected SW for send_apdu_checksw()
The method send_apdu_checksw() is used to check the SW of the final
response against some pre defined value. However, in many cases the
last two digits of the SW are used to return varying information
(e.g. length information or a more specific status info). To cover
those cases it would be helfpul to define status words that contain
wildcards (e.g. 61**) in order to be able to accept all SWs from
6100 to 61ff.

- When the user supplies an expected SW with wildcards, mask out
  those digits in the response before comparing

Change-Id: I5bfc0522b4228b5d9b3415f6e708abcf0da0a7b7
2018-06-18 09:43:56 +00:00
Philipp Maier
d9824887c9 pysim-prog: also allow raw hex adm pins besides numeric ascii
At the momemnt pysim takes the supplied ADM pin number and interprets
it as ascii string. This ascii string (max 8 digitis) is then padded
with 0xff bytes if necessary and sent to the card.

This limits the range of possible ADM keys and it is not possible
to deal with situataions where the ADM pin consists of arbitrary
bytes. At the momemnt pysim-prog forbis anything that is longer
than 8 digits. Lets also check if there are 16 digits and if yes
interpret them as raw bytes.

- when the adm pin is 16 digits long, interpret the string as raw
  bytes (hex).

Change-Id: If0ac0d328c64b57bc4d45d985a4a516930053344
Related: SYS#4245
2018-06-13 09:30:53 +02:00
Todd Neal
9eeadfc46b add support for open cells SIM cards
Change-Id: I4df5681952eefd7a67f5e2b0a96a9e01c9d960d2
2018-04-26 11:11:45 -05:00
Martin Hauke
6cbecaaf5c transport/serial: Fix serial transport
Fix a typo that broke the serial transport.

Change-Id: I7fcc97d505a5369f9f14d4a2abda92b7114a58cd
2018-02-18 17:04:50 +01:00
Harald Welte
d9d2b941eb Revert "pySim-prog: ADM code can be longer 8 digits, it's implementation specific."
This reverts commit a51592e180, which
broke the use of ADM pins on sysmoUSIM-SJS1 (and possibly others?)

The ADM pins have so far always been specified as ASCII decimal digits,
i.e. something like "-a 53204025" gets translated to hex "3533323034303235"

After the above patch this is broken and gets instead translated to
"53204025ffffffff" in hex which obviously breaks.  Let's revert back to
the old behavior to make it work again.

Change-Id: I3d68b7e09938a2fcb7a9a6a31048388cc3141f79
2018-01-23 16:25:55 +01:00
Alexander Chemeris
19fffa1db7 Make derive_milenage_opc and calculate_luhn publicly available through utils.py
Change-Id: I2effc85fd55da0981de0ada74dcb28b7e8e56a01
2018-01-11 13:06:43 +09:00
Alexander Chemeris
e0d9d88cd5 cards: Add Fairwaves SIM implementation.
Change-Id: Ia10ac433d3b0482bdf727c31f65a10042152797b
2018-01-10 17:16:31 +09:00
Alexander Chemeris
8ad124a0b8 cards: Implement card type autodetection based on ATR.
Change-Id: I1099a96626c0ce74243b47a8fdfa25b0d76a1ef3
2018-01-10 17:12:10 +09:00
Alexander Chemeris
47c73abd04 pySim-prog: Replace magic numbers with a readable EF file name.
Change-Id: Ibda7d5a4132971e884f6d760baf20cd33025a2af
2018-01-10 17:12:10 +09:00
Alexander Chemeris
a51592e180 pySim-prog: ADM code can be longer 8 digits, it's implementation specific.
E.g. Fairwaves SIM cards have longer ADM codes.

Change-Id: I87d61764eeba4bcf7525ee4778cb8f244930db9b
2018-01-10 17:12:10 +09:00
Alexander Chemeris
eb6807d3cb cards: Extend Card class with access functions for some of the standard EF files.
Change-Id: Icb7227fa7ebc837fccab456cbfad529f6ee81a28
2018-01-10 17:12:10 +09:00
Alexander Chemeris
d2d660a935 Add methods to get ATR for a card or a link.
Implemented for both serial and PCSC readers.

Change-Id: Ic12e4b115d24a8b7e483a5603dd6cec90ad289cc
2018-01-10 17:12:10 +09:00
Alexander Chemeris
dddbf525da utils: Fix documentation. 3+3=6 digits equals 3 bytes, not 6
Change-Id: I2722d788a69976e1c64a9caf6cf3049af27f9a30
2018-01-10 14:04:17 +09:00
Alexander Chemeris
a5f0ea6979 utils: Functions to encode/decode EF SPN.
According to TS 51 011.

Change-Id: Ida184bc5c81cc8c228b8981b703f77d017e53334
2018-01-10 14:04:11 +09:00
Alexander Chemeris
067f69cade ts_51_011: A file with MF/DF/EF constants from TS 51 011
pySim has been using magic numbers to access various files which makes it hard
to read, maintain and extend. With this file in place we can start replacing all
those magic numbers with human readable names lile EF['IMSI'] instead of
['3F00', '7F20', '6F07'].

Change-Id: I38f5d36d16b41b5d516a6a3e2ec1d09637883932
2018-01-10 14:04:08 +09:00
Alexander Chemeris
d17ca3ddd8 Fix comment: Ki -> OPC
Change-Id: I566cf7bc658c730b4381c0f145bfc4f805cca42a
2018-01-10 14:04:06 +09:00
Pau Espin Pedrol
665bd22fc5 utils.py: dec_imsi: Fix ValueError
It should fix the following observed error:
~/pysim$ ./pySim-read.py -p0
Reading ...
ICCID:
Traceback (most recent call last):
  File "./pySim-read.py", line 99, in <module>
    print("IMSI: %s" % (dec_imsi(res),))
  File "/home/lab434/pysim/pySim/utils.py", line 57, in dec_imsi
    l = int(ef[0:2]) * 2            # Length of the IMSI string
ValueError: invalid literal for int() with base 10: 'ff'

Change-Id: I7d3ecbf9edd190d1941816796cee60e3957d5943
2018-01-02 07:20:57 +00:00
Pau Espin Pedrol
287b6ce1b4 pySim-prog.py: Fix trailing whitespace
Change-Id: I735dc7bb774d77d3b60b1712b0f0afcbb81dc726
2017-12-29 23:35:22 +01:00
Pau Espin Pedrol
ac23ad5013 pySim-*.py: Set shebang to use python v2
Nowadays bin/python usually points to python3, and this script is written
in python2, which means if run directly from terminal it will fail with
some print syntax errors.

Change-Id: I6ab4e9edc44a8045915d4828c6de2fa98027fb7e
2017-12-29 23:35:22 +01:00
Daniel Willmann
1d087efc97 Support writing SMSP for sysmoUSIM-SJS1
Closes: OS#1989
Change-Id: I6cbf69be3d410c18a509b98a63cb69bab74a528a
2017-09-01 07:56:13 +00:00
Philipp Maier
e960488338 fix writing of ICCID for sysmo-usim-sjs1
The programming procedure for sysmo-usim-sjs1 lacks
writing the ICCID. This commit adds the missing call
to update_binary()

Change-Id: Ief85aa07c562d8d7b2a6dec302d2f485d0b1e577
2017-04-06 00:38:22 +02:00
Philipp Maier
4146086d2f Fix select control parameter
sysmo-usim-sjs1 requires P2 to be set to 0x0C (request FCI) when
using the USIM application commands. The FCI is not used by pysim
anyway and might even cause problems with other cards.

This commit adds a pair of get/set methods to the SimCardCommands
class in order to set a default for the selection control
parameters (P1, P2). (Similar to the set/get methods for the class
byte)

The SysmoUSIMSJS1 class now calls the setter method for the
selection control parameters inside of its constructuor and sets
the selection control parameter default to "000C". This way we
can be sure that we only change the behaviour for sysmo-usim-sjs1
and do not break support for any other cards.

Change-Id: I1993a267c952bf37d5de1cb4e1107f445614c17b
2017-04-06 00:38:22 +02:00
Neels Hofmeyr
3ce84d9a44 cosmetic: missing newlines on last line of 2 files
It's hard to keep this out of real patches, since normally editors add the
final newline automatically.
2017-03-21 13:07:57 +01:00
Harald Welte
9b7c45d05d README.md: Cosmetic/Formatting fixes 2017-03-17 22:34:08 +01:00
Harald Welte
42b6be8747 Update README with general project information and convert to Markdown 2017-03-17 18:08:09 +01:00
Harald Welte
9a1dcea7b3 Revert "Do not return the FCI information while selecting a file"
This reverts commit 8c1b33c439.
2016-10-27 14:40:02 +02:00
Holger Hans Peter Freyther
4e824686f5 re-program: Instead of specifying the IMSI, read it from the card. 2016-05-22 15:53:28 +02:00
Jan Balke
8c1b33c439 Do not return the FCI information while selecting a file
The sysmoUSIM-SJS1 card does not support returning the FCI
information.
Plus, the FCI information are not used anyway.
2015-08-20 13:32:56 +02:00
Jan Balke
3e84067a2b Add provision support for sysmoUSIM-SJS1 cards
The PIN-ADM has to given on the command line as it is provisioned
different for each card.
Currently only Ki, Op and IMSI are provisioned.
2015-08-20 13:32:56 +02:00
Jan Balke
c3ebd33544 Add PIN-ADM argument to the command line
Allow overwriting the default PIN-ADM set in the card implementation.
2015-08-20 13:32:56 +02:00
Jan Balke
14b350f3a1 Allow changing the class byte for pdu messages 2015-08-20 13:32:03 +02:00
Harald Welte
e9e5ecbe30 Introduce a '--dry-run' option to skip actual card access
This can be used for example to batch convert from CSV input to HLR
output without writing cards.
2015-08-20 13:23:15 +02:00
Harald Welte
c26b82939f read_params_csv: Make sure we don't end up in endless loop
as a side effect, the first line is now specified with '-j 0'
and not '-j 1'
2015-08-20 13:23:15 +02:00
Harald Welte
7f62cecb61 pySim-prog: Add mode where it can re-generate a card from CSV
Rather than just having the capability of writing to CSV, it now
has the capability to (re)write a card based on data from the CSV:

./pySim-prog.py -S csv --read-csv /tmp/sim.csv -i 901701234567890

or in batch mode (from the first line onwards):

./pySim-prog.py -S csv --read-csv /tmp/sim.csv --batch -j 1
2015-08-20 13:23:15 +02:00
Harald Welte
130524b719 split parameter writing for CSV and SQL into separate functions 2015-08-20 13:23:15 +02:00
Sylvain Munaut
2fc205ceb9 cards: sysmocom SysmoSIM-GR2 support added to cards
Written-by: Kevin Redon
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
2013-12-23 17:22:56 +01:00
Sylvain Munaut
9f13897408 pySim-read: MISDN is not mandatory
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
2013-07-18 10:36:51 +02:00
Alexander Chemeris
7be92ff5d2 Move encoder functions for ICCID, IMSI and PLMN to pySim.utils for consistency. 2013-07-13 08:53:21 +02:00
Alexander Chemeris
6e58914746 Add an utility to read data from a SIM card. 2013-07-13 08:50:55 +02:00
Alexander Chemeris
5e96c3d910 utils: Add functions to decode IMSI and ICCID from EF raw data. 2013-07-13 08:49:03 +02:00
Alexander Chemeris
21885249cf Implement setting of EF.ACC file in GrcardSim and _MagicSimBase
From: Alexander Chemeris <Alexander.Chemeris@gmail.com>
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
2013-07-02 15:19:09 +02:00
Sylvain Munaut
5da8d4e0d4 cards: Fix spacing according to PEP
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
2013-07-02 15:13:24 +02:00
Sylvain Munaut
053c89578c cards: Fix comment position
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
2013-07-02 15:12:32 +02:00
Holger Hans Peter Freyther
cca41795a7 usim/opc: Derive OPC from KI and OP 2012-03-22 15:25:31 +01:00
Harald Welte
93b38cd0f5 usim/opc: Add support to write completely random OPC
Allow to set the OPC, write it out to the state, generate it randomly.
2012-03-22 15:25:19 +01:00
Holger Hans Peter Freyther
4d91bf449f sysmoUSIM-GR1: Add basic (hacky) support for the sysmoUSIM-GR1
Right now we are only to program the KI, OPC, ICCID and IMSI. This
is done in a direct way and through the card abstraction.
2012-03-22 14:28:38 +01:00
Sylvain Munaut
8ca49e9ca8 cards: SMSP is left padded not right padded
I confused left & right, silly me ...

Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
2011-12-10 09:57:50 +01:00
Sylvain Munaut
9977c86e96 pySim-prog: Fix SMSC 'number type' field to 0x81
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
2011-12-10 09:57:16 +01:00
Sylvain Munaut
607ce2a029 Fix computation of SMSP from a SMSC number
Signed-off-by: Sylvain Munaut <tnt@246tNt.com>
2011-12-08 20:16:43 +01:00
Sylvain Munaut
1a914439b8 Whitespace fixes 2011-12-08 20:08:26 +01:00
Harald Welte
e10394bfb6 Add sysmoSIM-GR1 as alias to grcardsim 2011-12-08 19:39:49 +01:00
Harald Welte
2c0ff3a167 correctly compute the ICCID (19 digits, including luhn checksum) 2011-12-08 19:39:49 +01:00
Holger Hans Peter Freyther
5dffefbf0c pySim-prog.py: pyflakes doesn't find Importerror..
Importerror -> ImportError, spotted by pyflakes.
2011-11-22 21:18:06 +01:00
Holger Hans Peter Freyther
50e7c03816 README: Mention the necessary wait_for_card 2011-07-27 08:55:44 +02:00
Harald Welte
3156d9073f cards: Add support for grcard.cn (Green Card) SIM cards
This does not have auto-detection (yet), so you have to explicitly
specify "-t grcardsim" on the command line
2011-03-22 21:48:19 +01:00
Harald Welte
982a3075f9 commands: Check SW for UPDATE BIN / UPDATE REC / VERIFY CHV 2011-03-22 21:47:20 +01:00
28 changed files with 2398 additions and 141 deletions

3
.gitreview Normal file
View File

@@ -0,0 +1,3 @@
[gerrit]
host=gerrit.osmocom.org
project=pysim

36
README
View File

@@ -1,36 +0,0 @@
This utility allows to :
* Program customizable SIMs. Two modes are possible:
- one where you specify every parameter manually :
./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -i <IMSI> -s <ICCID>
- one where they are generated from some minimal set :
./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -z <random_string_of_choice> -j <card_num>
With <random_string_of_choice> and <card_num>, the soft will generate
'predictable' IMSI and ICCID, so make sure you choose them so as not to
conflict with anyone. (for eg. your name as <random_string_of_choice> and
0 1 2 ... for <card num>).
You also need to enter some parameters to select the device :
-t TYPE : type of card (supersim, magicsim, fakemagicsim or try 'auto')
-d DEV : Serial port device (default /dev/ttyUSB0)
-b BAUD : Baudrate (default 9600)
* Interact with SIMs from a python interactive shell (ipython for eg :)
from pySim.transport.serial import SerialSimLink
from pySim.commands import SimCardCommands
sl = SerialSimLink(device='/dev/ttyUSB0', baudrate=9600)
sc = SimCardCommands(sl)
# Print IMSI
print sc.read_binary(['3f00', '7f20', '6f07'])
# Run A3/A8
print sc.run_gsm('00112233445566778899aabbccddeeff')

107
README.md Normal file
View File

@@ -0,0 +1,107 @@
pySim-prog - Utility for programmable SIM/USIM-Cards
====================================================
This repository contains a Python-language program that can be used
to program (write) certain fields/parameters on so-called programmable
SIM/USIM cards.
Such SIM/USIM cards are special cards, which - unlike those issued by
regular commercial operators - come with the kind of keys that allow you
to write the files/fields that normally only an operator can program.
This is useful particularly if you are running your own cellular
network, and want to issue your own SIM/USIM cards for that network.
Homepage
--------
The official homepage of the project is
<http://osmocom.org/projects/pysim/wiki>
GIT Repository
--------------
You can clone from the official libosmocore.git repository using
git clone git://git.osmocom.org/pysim.git
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
------------
There is no separate mailing list for this project. However,
discussions related to pysim-prog are happening on the
openbsc@lists.osmocom.org mailing list, please see
<https://lists.osmocom.org/mailman/listinfo/openbsc> for subscription
options and the list archive.
Please observe the [Osmocom Mailing List
Rules](https://osmocom.org/projects/cellular-infrastructure/wiki/Mailing_List_Rules)
when posting.
Contributing
------------
Our coding standards are described at
<https://osmocom.org/projects/cellular-infrastructure/wiki/Coding_standards>
We are currently accepting patches by e-mail to the above-mentioned
mailing list.
Usage
-----
* Program customizable SIMs. Two modes are possible:
- one where you specify every parameter manually :
./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -i <IMSI> -s <ICCID>
- one where they are generated from some minimal set :
./pySim-prog.py -n 26C3 -c 49 -x 262 -y 42 -z <random_string_of_choice> -j <card_num>
With <random_string_of_choice> and <card_num>, the soft will generate
'predictable' IMSI and ICCID, so make sure you choose them so as not to
conflict with anyone. (for eg. your name as <random_string_of_choice> and
0 1 2 ... for <card num>).
You also need to enter some parameters to select the device :
-t TYPE : type of card (supersim, magicsim, fakemagicsim or try 'auto')
-d DEV : Serial port device (default /dev/ttyUSB0)
-b BAUD : Baudrate (default 9600)
* Interact with SIMs from a python interactive shell (ipython for eg :)
from pySim.transport.serial import SerialSimLink
from pySim.commands import SimCardCommands
sl = SerialSimLink(device='/dev/ttyUSB0', baudrate=9600)
sc = SimCardCommands(sl)
sl.wait_for_card()
# Print IMSI
print sc.read_binary(['3f00', '7f20', '6f07'])
# Run A3/A8
print sc.run_gsm('00112233445566778899aabbccddeeff')

18
contrib/jenkins.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/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

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python2
#
# Utility to deal with sim cards and program the 'magic' ones easily
@@ -33,14 +33,14 @@ import sys
try:
import json
except Importerror:
except ImportError:
# Python < 2.5
import simplejson as json
from pySim.commands import SimCardCommands
from pySim.cards import _cards_classes
from pySim.utils import h2b
from pySim.utils import h2b, swap_nibbles, rpad, derive_milenage_opc, calculate_luhn, dec_iccid
from pySim.ts_51_011 import EF
def parse_options():
@@ -58,15 +58,32 @@ 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)",
)
parser.add_option("-e", "--erase", dest="erase", action='store_true',
help="Erase beforehand [default: %default]",
default=False,
)
parser.add_option("-S", "--source", dest="source",
help="Data Source[default: %default]",
default="cmdline",
)
# if mode is "cmdline"
parser.add_option("-n", "--name", dest="name",
help="Operator name [default: %default]",
default="Magic",
@@ -83,8 +100,11 @@ def parse_options():
help="Mobile Network Code [default: %default]",
default=55,
)
parser.add_option("-m", "--smsp", dest="smsp",
help="SMSP [default: '00 + country code + 5555']",
parser.add_option("-m", "--smsc", dest="smsc",
help="SMSC number (Start with + for international no.) [default: '00 + country code + 5555']",
)
parser.add_option("-M", "--smsp", dest="smsp",
help="Raw SMSP content in hex [default: auto from SMSC]",
)
parser.add_option("-s", "--iccid", dest="iccid", metavar="ID",
@@ -96,7 +116,21 @@ def parse_options():
parser.add_option("-k", "--ki", dest="ki",
help="Ki (default is to randomize)",
)
parser.add_option("-o", "--opc", dest="opc",
help="OPC (default is to randomize)",
)
parser.add_option("--op", dest="op",
help="Set OP to derive OPC from OP and KI",
)
parser.add_option("--acc", dest="acc",
help="Set ACC bits (Access Control Code). not all card types are supported",
)
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",
)
@@ -111,12 +145,20 @@ def parse_options():
help="Optional batch state file",
)
# if mode is "csv"
parser.add_option("--read-csv", dest="read_csv", metavar="FILE",
help="Read parameters from CSV file rather than command line")
parser.add_option("--write-csv", dest="write_csv", metavar="FILE",
help="Append generated parameters in CSV file",
)
parser.add_option("--write-hlr", dest="write_hlr", metavar="FILE",
help="Append generated parameters to OpenBSC HLR sqlite3",
)
parser.add_option("--dry-run", dest="dry_run",
help="Perform a 'dry run', don't actually program the card",
default=False, action="store_true")
(options, args) = parser.parse_args()
@@ -125,6 +167,23 @@ 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.read_csv is None:
parser.error("CSV mode requires a CSV input file")
elif options.source == 'cmdline':
if ((options.imsi is None) or (options.iccid is None)) and (options.num is None):
parser.error("If either IMSI or ICCID isn't specified, num is required")
else:
parser.error("Only `cmdline' and `csv' sources supported")
if (options.read_csv is not None) and (options.source != 'csv'):
parser.error("You cannot specify a CSV input file in source != csv")
if (options.batch_mode) and (options.num is None):
options.num = 0
@@ -132,9 +191,6 @@ def parse_options():
if (options.imsi is not None) or (options.iccid is not None):
parser.error("Can't give ICCID/IMSI for batch mode, need to use automatic parameters ! see --num and --secret for more informations")
if ((options.imsi is None) or (options.iccid is None)) and (options.num is None):
parser.error("If either IMSI or ICCID isn't specified, num is required")
if args:
parser.error("Extraneous arguments")
@@ -155,6 +211,10 @@ def _cc_digits(cc):
def _isnum(s, l=-1):
return s.isdigit() and ((l== -1) or (len(s) == l))
def _ishex(s, l=-1):
hc = '0123456789abcdef'
return all([x in hc for x in s.lower()]) and ((l== -1) or (len(s) == l))
def _dbi_binary_quote(s):
# Count usage of each char
@@ -174,7 +234,7 @@ def _dbi_binary_quote(s):
e = i
if m == 0: # No overhead ? use this !
break;
# Generate output
out = []
out.append( chr(e) ) # Offset
@@ -188,9 +248,8 @@ def _dbi_binary_quote(s):
return ''.join(out)
def gen_parameters(opts):
"""Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki from the
"""Generates Name, ICCID, MCC, MNC, IMSI, SMSP, Ki, PIN-ADM from the
options given by the user"""
# MCC/MNC
@@ -206,11 +265,11 @@ def gen_parameters(opts):
# Digitize MCC/MNC (5 or 6 digits)
plmn_digits = _mcc_mnc_digits(mcc, mnc)
# ICCID (20 digits)
# ICCID (19 digits, E.118), though some phase1 vendors use 20 :(
if opts.iccid is not None:
iccid = opts.iccid
if not _isnum(iccid, 20):
raise ValueError('ICCID must be 20 digits !');
if not _isnum(iccid, 19) and not _isnum(iccid, 20):
raise ValueError('ICCID must be 19 or 20 digits !');
else:
if opts.num is None:
@@ -222,7 +281,7 @@ def gen_parameters(opts):
plmn_digits # MCC/MNC on 5/6 digits
)
ml = 20 - len(iccid)
ml = 18 - len(iccid)
if opts.secret is None:
# The raw number
@@ -231,6 +290,9 @@ def gen_parameters(opts):
# Randomized digits
iccid += _digits(opts.secret, 'ccid', ml, opts.num)
# Add checksum digit
iccid += ('%1d' % calculate_luhn(iccid))
# IMSI (15 digits usually)
if opts.imsi is not None:
imsi = opts.imsi
@@ -258,21 +320,77 @@ def gen_parameters(opts):
# SMSP
if opts.smsp is not None:
smsp = opts.smsp
if not _isnum(smsp):
raise ValueError('SMSP must be digits only !')
if not _ishex(smsp):
raise ValueError('SMSP must be hex digits only !')
if len(smsp) < 28*2:
raise ValueError('SMSP must be at least 28 bytes')
else:
smsp = '00%d' % opts.country + '5555' # Hack ...
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')
else:
smsc = '00%d' % opts.country + '5555' # Hack ...
smsc = '%02d' % ((len(smsc) + 3)//2,) + ton + swap_nibbles(rpad(smsc, 20))
smsp = (
'e1' + # Parameters indicator
'ff' * 12 + # TP-Destination address
smsc + # TP-Service Centre Address
'00' + # TP-Protocol identifier
'00' + # TP-Data coding scheme
'00' # TP-Validity period
)
# ACC
if opts.acc is not None:
acc = opts.acc
if not _ishex(acc):
raise ValueError('ACC must be hex digits only !')
if len(acc) != 2*2:
raise ValueError('ACC must be exactly 2 bytes')
else:
acc = None
# Ki (random)
if opts.ki is not None:
ki = opts.ki
if not re.match('^[0-9a-fA-F]{32}$', ki):
raise ValueError('Ki needs to be 128 bits, in hex format')
else:
ki = ''.join(['%02x' % random.randrange(0,256) for i in range(16)])
# OPC (random)
if opts.opc is not None:
opc = opts.opc
if not re.match('^[0-9a-fA-F]{32}$', opc):
raise ValueError('OPC needs to be 128 bits, in hex format')
elif opts.op is not None:
opc = derive_milenage_opc(ki, opts.op)
else:
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)")
else:
pin_adm = None
# Return that
return {
'name' : opts.name,
@@ -282,6 +400,9 @@ def gen_parameters(opts):
'imsi' : imsi,
'smsp' : smsp,
'ki' : ki,
'opc' : opc,
'acc' : acc,
'adm1' : pin_adm,
}
@@ -294,19 +415,55 @@ def print_parameters(params):
> MCC/MNC : %(mcc)d/%(mnc)d
> IMSI : %(imsi)s
> Ki : %(ki)s
> OPC : %(opc)s
> ACC : %(acc)s
> ADM1 : %(adm1)s
""" % params
def write_parameters(opts, params):
# CSV
def write_params_csv(opts, params):
# csv
if opts.write_csv:
import csv
row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki']
row = ['name', 'iccid', 'mcc', 'mnc', 'imsi', 'smsp', 'ki', 'opc']
f = open(opts.write_csv, 'a')
cw = csv.writer(f)
cw.writerow([params[x] for x in row])
f.close()
def _read_params_csv(opts, iccid=None, imsi=None):
import csv
f = open(opts.read_csv, 'r')
cr = csv.DictReader(f)
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 == i:
f.close()
return row;
i += 1
if row['iccid'] == iccid:
f.close()
return row;
if row['imsi'] == imsi:
f.close()
return row;
f.close()
return None
def read_params_csv(opts, imsi=None, iccid=None):
row = _read_params_csv(opts, iccid=iccid, imsi=imsi)
if row is not None:
row['mcc'] = int(row['mcc'])
row['mnc'] = int(row['mnc'])
return row
def write_params_hlr(opts, params):
# SQLite3 OpenBSC HLR
if opts.write_hlr:
import sqlite3
@@ -320,7 +477,7 @@ def write_parameters(opts, params):
[
params['imsi'],
params['name'],
'9' + params['iccid'][-5:]
'9' + params['iccid'][-5:-1]
],
)
sub_id = c.lastrowid
@@ -337,6 +494,10 @@ def write_parameters(opts, params):
conn.commit()
conn.close()
def write_parameters(opts, params):
write_params_csv(opts, params)
write_params_hlr(opts, params)
BATCH_STATE = [ 'name', 'country', 'mcc', 'mnc', 'smsp', 'secret', 'num' ]
BATCH_INCOMPATIBLE = ['iccid', 'imsi', 'ki']
@@ -389,7 +550,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
@@ -404,7 +565,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
@@ -414,13 +575,22 @@ 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:
# 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))
from pySim.transport.serial import SerialSimLink
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
# Create command layer
scc = SimCardCommands(transport=sl)
@@ -432,37 +602,73 @@ if __name__ == '__main__':
done = False
first = True
card = None
while not done:
# Connect transport
print "Insert card now (or CTRL-C to cancel)"
sl.wait_for_card(newcardonly=not first)
if opts.dry_run is False:
# Connect transport
print "Insert card now (or CTRL-C to cancel)"
sl.wait_for_card(newcardonly=not first)
# Not the first anymore !
first = False
# Get card
card = card_detect(opts, scc)
if card is None:
if opts.batch_mode:
first = False
continue
else:
sys.exit(-1)
if opts.dry_run is False:
# Get card
card = card_detect(opts, scc)
if card is None:
if opts.batch_mode:
first = False
continue
else:
sys.exit(-1)
# Erase if requested
if opts.erase:
print "Formatting ..."
card.erase()
card.reset()
# Probe only
if opts.probe:
break;
# Erase if requested
if opts.erase:
print "Formatting ..."
card.erase()
card.reset()
# Generate parameters
cp = gen_parameters(opts)
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.dry_run:
# Connect transport
print "Insert card now (or CTRL-C to cancel)"
sl.wait_for_card(newcardonly=not first)
(res,_) = scc.read_binary(EF['IMSI'])
imsi = swap_nibbles(res)[3:]
else:
imsi = opts.imsi
cp = read_params_csv(opts, imsi=imsi, iccid=iccid)
if cp is None:
print "Error reading parameters\n"
sys.exit(2)
print_parameters(cp)
# Program the card
print "Programming ..."
card.program(cp)
if opts.dry_run is False:
# Program the card
print "Programming ..."
if opts.dry_run is not True:
card.program(cp)
else:
print "Dry Run: NOT PROGRAMMING!"
# Write parameters permanently
write_parameters(opts, cp)
@@ -477,4 +683,3 @@ if __name__ == '__main__':
if not opts.batch_mode:
done = True

193
pySim-read.py Executable file
View File

@@ -0,0 +1,193 @@
#!/usr/bin/env python2
#
# Utility to display some informations about 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
from pySim.ts_51_011 import EF, DF
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, format_xplmn_w_act
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,
)
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()
if args:
parser.error("Extraneous arguments")
return options
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))
from pySim.transport.serial import SerialSimLink
sl = SerialSimLink(device=opts.device, baudrate=opts.baudrate)
# Create command layer
scc = SimCardCommands(transport=sl)
# Wait for SIM card
sl.wait_for_card()
# Program the card
print("Reading ...")
# EF.ICCID
(res, sw) = scc.read_binary(EF['ICCID'])
if sw == '9000':
print("ICCID: %s" % (dec_iccid(res),))
else:
print("ICCID: Can't read, response code = %s" % (sw,))
# EF.IMSI
(res, sw) = scc.read_binary(['3f00', '7f20', '6f07'])
if sw == '9000':
print("IMSI: %s" % (dec_imsi(res),))
else:
print("IMSI: Can't read, response code = %s" % (sw,))
# EF.SMSP
(res, sw) = scc.read_record(['3f00', '7f10', '6f42'], 1)
if sw == '9000':
print("SMSP: %s" % (res,))
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.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.ACC
(res, sw) = scc.read_binary(['3f00', '7f20', '6f78'])
if sw == '9000':
print("ACC: %s" % (res,))
else:
print("ACC: Can't read, response code = %s" % (sw,))
# EF.MSISDN
try:
# print(scc.record_size(['3f00', '7f10', '6f40']))
(res, sw) = scc.read_record(['3f00', '7f10', '6f40'], 1)
if sw == '9000':
if res[1] != 'f':
print("MSISDN: %s" % (res,))
else:
print("MSISDN: Not available")
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,))
# Done for this card and maybe for everything ?
print "Done !\n"

View File

@@ -0,0 +1 @@

View File

@@ -6,6 +6,8 @@
#
# Copyright (C) 2009-2010 Sylvain Munaut <tnt@246tNt.com>
# Copyright (C) 2011 Harald Welte <laforge@gnumonks.org>
# Copyright (C) 2017 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
@@ -21,31 +23,127 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from pySim.utils import b2h, swap_nibbles, rpad, lpad
from pySim.ts_51_011 import EF, DF
from pySim.utils import *
from smartcard.util import toBytes
class Card(object):
def __init__(self, scc):
self._scc = scc
def _e_iccid(self, iccid):
return swap_nibbles(iccid)
def _e_imsi(self, imsi):
"""Converts a string imsi into the value of the EF"""
l = (len(imsi) + 1) // 2 # Required bytes
oe = len(imsi) & 1 # Odd (1) / Even (0)
ei = '%02x' % l + swap_nibbles(lpad('%01x%s' % ((oe<<3)|1, imsi), 16))
return ei
def _e_plmn(self, mcc, mnc):
"""Converts integer MCC/MNC into 6 bytes for EF"""
return swap_nibbles(lpad('%d' % mcc, 3) + lpad('%d' % mnc, 3))
self._adm_chv_num = 4
def reset(self):
self._scc.reset_card()
def verify_adm(self, key):
'''
Authenticate with ADM key
'''
(res, sw) = self._scc.verify_chv(self._adm_chv_num, key)
return sw
def read_iccid(self):
(res, sw) = self._scc.read_binary(EF['ICCID'])
if sw == '9000':
return (dec_iccid(res), sw)
else:
return (None, sw)
def read_imsi(self):
(res, sw) = self._scc.read_binary(EF['IMSI'])
if sw == '9000':
return (dec_imsi(res), sw)
else:
return (None, sw)
def update_imsi(self, imsi):
data, sw = self._scc.update_binary(EF['IMSI'], enc_imsi(imsi))
return sw
def update_acc(self, acc):
data, sw = self._scc.update_binary(EF['ACC'], lpad(acc, 4))
return sw
def update_hplmn_act(self, mcc, mnc, access_tech='FFFF'):
"""
Update Home PLMN with access technology bit-field
See Section "10.3.37 EFHPLMNwAcT (HPLMN Selector with Access Technology)"
in ETSI TS 151 011 for the details of the access_tech field coding.
Some common values:
access_tech = '0080' # Only GSM is selected
access_tech = 'FFFF' # All technologues selected, even Reserved for Future Use ones
"""
# get size and write EF.HPLMNwAcT
r = self._scc.select_file(EF['HPLMNwAcT'])
size = int(r[-1][4:8], 16)
hplmn = enc_plmn(mcc, mnc)
content = hplmn + access_tech
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':
return (dec_spn(spn), sw)
else:
return (None, sw)
def update_spn(self, name, hplmn_disp=False, oplmn_disp=False):
content = enc_spn(name, hplmn_disp, oplmn_disp)
data, sw = self._scc.update_binary(EF['SPN'], rpad(content, 32))
return sw
class _MagicSimBase(Card):
"""
@@ -102,7 +200,7 @@ class _MagicSimBase(Card):
self._scc.select_file(['3f00', '7f4d'])
# Home PLMN in PLMN_Sel format
hplmn = self._e_plmn(p['mcc'], p['mnc'])
hplmn = enc_plmn(p['mcc'], p['mnc'])
# Operator name ( 3f00/7f4d/8f0c )
self._scc.update_record(self._files['name'][0], 2,
@@ -117,10 +215,10 @@ class _MagicSimBase(Card):
v += p['ki']
# ICCID
v += '3f00' + '2fe2' + '0a' + self._e_iccid(p['iccid'])
v += '3f00' + '2fe2' + '0a' + enc_iccid(p['iccid'])
# IMSI
v += '7f20' + '6f07' + '09' + self._e_imsi(p['imsi'])
v += '7f20' + '6f07' + '09' + enc_imsi(p['imsi'])
# Ki
if self._ki_file:
@@ -129,6 +227,12 @@ class _MagicSimBase(Card):
# PLMN_Sel
v+= '6f30' + '18' + rpad(hplmn, 36)
# ACC
# This doesn't work with "fake" SuperSIM cards,
# but will hopefully work with real SuperSIMs.
if p.get('acc') is not None:
v+= '6f78' + '02' + lpad(p['acc'], 4)
self._scc.update_record(self._files['b_ef'][0], 1,
rpad(v, self._files['b_ef'][1]*2)
)
@@ -140,7 +244,7 @@ class _MagicSimBase(Card):
r = self._scc.select_file(['3f00', '7f20', '6f30'])
tl = int(r[-1][4:8], 16)
hplmn = self._e_plmn(p['mcc'], p['mnc'])
hplmn = enc_plmn(p['mcc'], p['mnc'])
self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3))
def erase(self):
@@ -226,7 +330,7 @@ class FakeMagicSim(Card):
r = self._scc.select_file(['3f00', '7f20', '6f30'])
tl = int(r[-1][4:8], 16)
hplmn = self._e_plmn(p['mcc'], p['mnc'])
hplmn = enc_plmn(p['mcc'], p['mnc'])
self._scc.update_binary('6f30', hplmn + 'ff' * (tl-3))
# Get total number of entries and entry size
@@ -234,14 +338,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
self._e_iccid(p['iccid']) + # 10b ICCID
self._e_imsi(p['imsi']) + # 9b IMSI_len + id_type(9) + IMSI
p['ki'] + # 16b Ki
24*'f' + 'fd' + 24*'f' + # 25b (unknown ...)
rpad(p['smsp'], 20) + # 10b SMSP (padded with ff if needed)
10*'f' # 5b (unknown ...)
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)
@@ -255,5 +357,569 @@ class FakeMagicSim(Card):
self._scc.update_record('000c', 1+i, entry)
# In order for autodetection ...
_cards_classes = [ FakeMagicSim, SuperSim, MagicSim ]
class GrcardSim(Card):
"""
Greencard (grcard.cn) HZCOS GSM SIM
These cards have a much more regular ISO 7816-4 / TS 11.11 structure,
and use standard UPDATE RECORD / UPDATE BINARY commands except for Ki.
"""
name = 'grcardsim'
@classmethod
def autodetect(kls, scc):
return None
def program(self, p):
# We don't really know yet what ADM PIN 4 is about
#self._scc.verify_chv(4, h2b("4444444444444444"))
# Authenticate using ADM PIN 5
if p['adm1']:
pin = h2b(p['adm1'])
else:
pin = h2b("4444444444444444")
self._scc.verify_chv(5, pin)
# EF.ICCID
r = self._scc.select_file(['3f00', '2fe2'])
data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid']))
# EF.IMSI
r = self._scc.select_file(['3f00', '7f20', '6f07'])
data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
# EF.ACC
if p.get('acc') is not None:
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))
# Set the Ki using proprietary command
pdu = '80d4020010' + p['ki']
data, sw = self._scc._tp.send_apdu(pdu)
# EF.HPLMN
r = self._scc.select_file(['3f00', '7f20', '6f30'])
size = int(r[-1][4:8], 16)
hplmn = enc_plmn(p['mcc'], p['mnc'])
self._scc.update_binary('6f30', hplmn + 'ff' * (size-3))
# EF.SPN (Service Provider Name)
r = self._scc.select_file(['3f00', '7f20', '6f30'])
size = int(r[-1][4:8], 16)
# FIXME
# FIXME: EF.MSISDN
def erase(self):
return
class SysmoSIMgr1(GrcardSim):
"""
sysmocom sysmoSIM-GR1
These cards have a much more regular ISO 7816-4 / TS 11.11 structure,
and use standard UPDATE RECORD / UPDATE BINARY commands except for Ki.
"""
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):
"""
sysmocom sysmoUSIM-GR1
"""
name = 'sysmoUSIM-GR1'
@classmethod
def autodetect(kls, scc):
# TODO: Access the ATR
return None
def program(self, p):
# TODO: check if verify_chv could be used or what it needs
# self._scc.verify_chv(0x0A, [0x33,0x32,0x32,0x31,0x33,0x32,0x33,0x32])
# Unlock the card..
data, sw = self._scc._tp.send_apdu_checksw("0020000A083332323133323332")
# TODO: move into SimCardCommands
par = ( p['ki'] + # 16b K
p['opc'] + # 32b OPC
enc_iccid(p['iccid']) + # 10b ICCID
enc_imsi(p['imsi']) # 9b IMSI_len + id_type(9) + IMSI
)
data, sw = self._scc._tp.send_apdu_checksw("0099000033" + par)
def erase(self):
return
class SysmoSIMgr2(Card):
"""
sysmocom sysmoSIM-GR2
"""
name = 'sysmoSIM-GR2'
@classmethod
def autodetect(kls, scc):
try:
# Look for ATR
if scc.get_atr() == toBytes("3B 7D 94 00 00 55 55 53 0A 74 86 93 0B 24 7C 4D 54 68"):
return kls(scc)
except:
return None
return None
def program(self, p):
# select MF
r = self._scc.select_file(['3f00'])
# authenticate as SUPER ADM using default key
self._scc.verify_chv(0x0b, h2b("3838383838383838"))
# set ADM pin using proprietary command
# INS: D4
# 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']
else:
pin = h2b("4444444444444444")
pdu = 'A0D43A0508' + b2h(pin)
data, sw = self._scc._tp.send_apdu(pdu)
# authenticate as ADM (enough to write file, and can set PINs)
self._scc.verify_chv(0x05, pin)
# write EF.ICCID
data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid']))
# select DF_GSM
r = self._scc.select_file(['7f20'])
# write EF.IMSI
data, sw = self._scc.update_binary('6f07', enc_imsi(p['imsi']))
# write EF.ACC
if p.get('acc') is not None:
data, sw = self._scc.update_binary('6f78', lpad(p['acc'], 4))
# get size and write EF.HPLMN
r = self._scc.select_file(['6f30'])
size = int(r[-1][4:8], 16)
hplmn = enc_plmn(p['mcc'], p['mnc'])
self._scc.update_binary('6f30', hplmn + 'ff' * (size-3))
# set COMP128 version 0 in proprietary file
data, sw = self._scc.update_binary('0001', '001000')
# set Ki in proprietary file
data, sw = self._scc.update_binary('0001', p['ki'], 3)
# select DF_TELECOM
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))
def erase(self):
return
class SysmoUSIMSJS1(Card):
"""
sysmocom sysmoUSIM-SJS1
"""
name = 'sysmoUSIM-SJS1'
def __init__(self, ssc):
super(SysmoUSIMSJS1, self).__init__(ssc)
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 96 80 1F C7 80 31 A0 73 BE 21 13 67 43 20 07 18 00 00 01 A5"):
return kls(scc)
except:
return None
return None
def program(self, p):
# authenticate as ADM using default key (written on the card..)
if not p['adm1']:
raise ValueError("Please provide a PIN-ADM as there is no default one")
self._scc.verify_chv(0x0A, p['adm1'])
# select MF
r = self._scc.select_file(['3f00'])
# write EF.ICCID
data, sw = self._scc.update_binary('2fe2', enc_iccid(p['iccid']))
# select DF_GSM
r = self._scc.select_file(['7f20'])
# 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)
# 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)
def erase(self):
return
class FairwavesSIM(Card):
"""
FairwavesSIM
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;
3F00/7F20/FF02: Ki
"""
name = 'Fairwaves SIM'
# Propriatary files
_EF_num = {
'Ki': 'FF02',
'OP/OPC': 'FF01',
}
_EF = {
'Ki': DF['GSM']+[_EF_num['Ki']],
'OP/OPC': DF['GSM']+[_EF_num['OP/OPC']],
}
def __init__(self, ssc):
super(FairwavesSIM, self).__init__(ssc)
self._adm_chv_num = 0x11
self._adm2_chv_num = 0x12
@classmethod
def autodetect(kls, scc):
try:
# Look for ATR
if scc.get_atr() == toBytes("3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 67 44 22 06 10 00 00 01 A9"):
return kls(scc)
except:
return None
return None
def verify_adm2(self, key):
'''
Authenticate with ADM2 key.
Fairwaves SIM cards support hierarchical key structure and ADM2 key
is a key which has access to proprietary files (Ki and OP/OPC).
That said, ADM key inherits permissions of ADM2 key and thus we rarely
need ADM2 key per se.
'''
(res, sw) = self._scc.verify_chv(self._adm2_chv_num, key)
return sw
def read_ki(self):
"""
Read Ki in proprietary file.
Requires ADM1 access level
"""
return self._scc.read_binary(self._EF['Ki'])
def update_ki(self, ki):
"""
Set Ki in proprietary file.
Requires ADM1 access level
"""
data, sw = self._scc.update_binary(self._EF['Ki'], ki)
return sw
def read_op_opc(self):
"""
Read Ki in proprietary file.
Requires ADM1 access level
"""
(ef, sw) = self._scc.read_binary(self._EF['OP/OPC'])
type = 'OP' if ef[0:2] == '00' else 'OPC'
return ((type, ef[2:]), sw)
def update_op(self, op):
"""
Set OP in proprietary file.
Requires ADM1 access level
"""
content = '00' + op
data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
return sw
def update_opc(self, opc):
"""
Set OPC in proprietary file.
Requires ADM1 access level
"""
content = '01' + opc
data, sw = self._scc.update_binary(self._EF['OP/OPC'], content)
return sw
def program(self, p):
# authenticate as ADM1
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'],))
# TODO: Set operator name
if p.get('smsp') is not None:
sw = self.update_smsp(p['smsp'])
if sw != '9000':
print("Programming SMSP failed with code %s"%sw)
# This SIM doesn't support changing ICCID
if p.get('mcc') is not None and p.get('mnc') is not None:
sw = self.update_hplmn_act(p['mcc'], p['mnc'])
if sw != '9000':
print("Programming MCC/MNC failed with code %s"%sw)
if p.get('imsi') is not None:
sw = self.update_imsi(p['imsi'])
if sw != '9000':
print("Programming IMSI failed with code %s"%sw)
if p.get('ki') is not None:
sw = self.update_ki(p['ki'])
if sw != '9000':
print("Programming Ki failed with code %s"%sw)
if p.get('opc') is not None:
sw = self.update_opc(p['opc'])
if sw != '9000':
print("Programming OPC failed with code %s"%sw)
if p.get('acc') is not None:
sw = self.update_acc(p['acc'])
if sw != '9000':
print("Programming ACC failed with code %s"%sw)
def erase(self):
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 ...
_cards_classes = [ FakeMagicSim, SuperSim, MagicSim, GrcardSim,
SysmoSIMgr1, SysmoSIMgr2, SysmoUSIMgr1, SysmoUSIMSJS1,
FairwavesSIM, OpenCellsSim, WavemobileSim ]
def card_autodetect(scc):
for kls in _cards_classes:
card = kls.autodetect(scc)
if card is not None:
card.reset()
return card
return None

View File

@@ -24,15 +24,84 @@
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()
@property
def cla_byte(self):
return self._cla_byte
@cla_byte.setter
def cla_byte(self, value):
self._cla_byte = value
@property
def sel_ctrl(self):
return self._sel_ctrl
@sel_ctrl.setter
def sel_ctrl(self, value):
self._sel_ctrl = value
def select_file(self, dir_list):
rv = []
for i in dir_list:
data, sw = self._tp.send_apdu_checksw("a0a4000002" + i)
data, sw = self._tp.send_apdu_checksw(self.cla_byte + "a4" + self.sel_ctrl + "02" + i)
rv.append(data)
return rv
@@ -40,24 +109,26 @@ class SimCardCommands(object):
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 = int(r[-1][4:8], 16) - offset
pdu = 'a0b0%04x%02x' % (offset, (min(256, length) & 0xff))
length = self.__len(r) - offset
pdu = self.cla_byte + 'b0%04x%02x' % (offset, (min(256, length) & 0xff))
return self._tp.send_apdu(pdu)
def update_binary(self, ef, data, offset=0):
if not hasattr(type(ef), '__iter__'):
ef = [ef]
self.select_file(ef)
pdu = 'a0d6%04x%02x' % (offset, len(data)/2) + data
return self._tp.send_apdu(pdu)
pdu = self.cla_byte + 'd6%04x%02x' % (offset, len(data)/2) + data
return self._tp.send_apdu_checksw(pdu)
def read_record(self, ef, rec_no):
if not hasattr(type(ef), '__iter__'):
ef = [ef]
r = self.select_file(ef)
rec_length = int(r[-1][28:30], 16)
pdu = 'a0b2%02x04%02x' % (rec_no, rec_length)
rec_length = self.__record_len(r)
pdu = self.cla_byte + 'b2%02x04%02x' % (rec_no, rec_length)
return self._tp.send_apdu(pdu)
def update_record(self, ef, rec_no, data, force_len=False):
@@ -65,31 +136,31 @@ class SimCardCommands(object):
ef = [ef]
r = self.select_file(ef)
if not force_len:
rec_length = int(r[-1][28:30], 16)
rec_length = self.__record_len(r)
if (len(data)/2 != rec_length):
raise ValueError('Invalid data length (expected %d, got %d)' % (rec_length, len(data)/2))
else:
rec_length = len(data)/2
pdu = ('a0dc%02x04%02x' % (rec_no, rec_length)) + data
return self._tp.send_apdu(pdu)
pdu = (self.cla_byte + 'dc%02x04%02x' % (rec_no, rec_length)) + data
return self._tp.send_apdu_checksw(pdu)
def record_size(self, ef):
r = self.select_file(ef)
return int(r[-1][28:30], 16)
return self.__record_len(r)
def record_count(self, ef):
r = self.select_file(ef)
return int(r[-1][4:8], 16) // int(r[-1][28:30], 16)
return self.__len(r) // self.__record_len(r)
def run_gsm(self, rand):
if len(rand) != 32:
raise ValueError('Invalid rand')
self.select_file(['3f00', '7f20'])
return self._tp.send_apdu('a088000010' + rand)
return self._tp.send_apdu(self.cla_byte + '88000010' + rand)
def reset_card(self):
return self._tp.reset_card()
def verify_chv(self, chv_no, code):
fc = rpad(b2h(code), 16)
return self._tp.send_apdu('a02000' + ('%02x' % chv_no) + '08' + fc)
return self._tp.send_apdu_checksw(self.cla_byte + '2000' + ('%02X' % chv_no) + '08' + fc)

View File

@@ -31,3 +31,6 @@ class NoCardError(exceptions.Exception):
class ProtocolError(exceptions.Exception):
pass
class ReaderError(exceptions.Exception):
pass

View File

@@ -67,7 +67,13 @@ class LinkBase(object):
"""
data, sw = self.send_apdu_raw(pdu)
if (sw is not None) and (sw[0:2] == '9f'):
# 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')):
pdu_gr = pdu[0:2] + 'c00000' + sw[2:4]
data, sw = self.send_apdu_raw(pdu_gr)
@@ -77,12 +83,23 @@ 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")
sw : string of 4 hexadecimal characters (ex. "9000"). The
user may mask out certain digits using a '?' to add some
ambiguity if needed.
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)
if sw.lower() != rv[1]:
raise RuntimeError("SW match failed ! Expected %s and got %s." % (sw.lower(), rv[1]))
# 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]))
return rv

157
pySim/transport/calypso.py Normal file
View File

@@ -0,0 +1,157 @@
#!/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)

View File

@@ -56,6 +56,9 @@ class PcscSimLink(LinkBase):
except NoCardException:
raise NoCardError()
def get_atr(self):
return self._con.getATR()
def disconnect(self):
self._con.disconnect()

View File

@@ -46,6 +46,7 @@ class SerialSimLink(LinkBase):
)
self._rst_pin = rst
self._debug = debug
self._atr = None
def __del__(self):
self._sl.close()
@@ -91,6 +92,9 @@ class SerialSimLink(LinkBase):
def connect(self):
self.reset_card()
def get_atr(self):
return self._atr
def disconnect(self):
pass # Nothing to do really ...
@@ -102,6 +106,7 @@ class SerialSimLink(LinkBase):
raise ProtocolError()
def _reset_card(self):
self._atr = None
rst_meth_map = {
'rts': self._sl.setRTS,
'dtr': self._sl.setDTR,
@@ -133,18 +138,24 @@ class SerialSimLink(LinkBase):
return -1
t0 = ord(b)
self._dbg_print("T0: 0x%x" % t0)
self._atr = [0x3b, ord(b)]
for i in range(4):
if t0 & (0x10 << i):
self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(self._rx_byte())))
b = self._rx_byte()
self._atr.append(ord(b))
self._dbg_print("T%si = %x" % (chr(ord('A')+i), ord(b)))
for i in range(0, t0 & 0xf):
self._dbg_print("Historical = %x" % ord(self._rx_byte()))
b = self._rx_byte()
self._atr.append(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._dbg_print("Extra: %x" % ord(x))
return 1

251
pySim/ts_51_011.py Normal file
View File

@@ -0,0 +1,251 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""" Various constants from ETSI TS 151.011
"""
#
# Copyright (C) 2017 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/>.
#
MF_num = '3F00'
DF_num = {
'TELECOM': '7F10',
'GSM': '7F20',
'IS-41': '7F22',
'FP-CTS': '7F23',
'GRAPHICS': '5F50',
'IRIDIUM': '5F30',
'GLOBST': '5F31',
'ICO': '5F32',
'ACeS': '5F33',
'EIA/TIA-553': '5F40',
'CTS': '5F60',
'SOLSA': '5F70',
'MExE': '5F3C',
}
EF_num = {
# MF
'ICCID': '2FE2',
'ELP': '2F05',
# DF_TELECOM
'ADN': '6F3A',
'FDN': '6F3B',
'SMS': '6F3C',
'CCP': '6F3D',
'MSISDN': '6F40',
'SMSP': '6F42',
'SMSS': '6F43',
'LND': '6F44',
'SMSR': '6F47',
'SDN': '6F49',
'EXT1': '6F4A',
'EXT2': '6F4B',
'EXT3': '6F4C',
'BDN': '6F4D',
'EXT4': '6F4E',
'CMI': '6F58',
'ECCP': '6F4F',
# DF_GRAPHICS
'IMG': '4F20',
# DF_SoLSA
'SAI': '4F30',
'SLL': '4F31',
# DF_MExE
'MExE-ST': '4F40',
'ORPK': '4F41',
'ARPK': '4F42',
'TPRPK': '4F43',
# DF_GSM
'LP': '6F05',
'IMSI': '6F07',
'Kc': '6F20',
'DCK': '6F2C',
'PLMNsel': '6F30',
'HPPLMN': '6F31',
'CNL': '6F32',
'ACMmax': '6F37',
'SST': '6F38',
'ACM': '6F39',
'GID1': '6F3E',
'GID2': '6F3F',
'PUCT': '6F41',
'CBMI': '6F45',
'SPN': '6F46',
'CBMID': '6F48',
'BCCH': '6F74',
'ACC': '6F78',
'FPLMN': '6F7B',
'LOCI': '6F7E',
'AD': '6FAD',
'PHASE': '6FAE',
'VGCS': '6FB1',
'VGCSS': '6FB2',
'VBS': '6FB3',
'VBSS': '6FB4',
'eMLPP': '6FB5',
'AAeM': '6FB6',
'ECC': '6FB7',
'CBMIR': '6F50',
'NIA': '6F51',
'KcGPRS': '6F52',
'LOCIGPRS': '6F53',
'SUME': '6F54',
'PLMNwAcT': '6F60',
'OPLMNwAcT': '6F61',
# Figure 8 names it HPLMNAcT, but in the text it's names it HPLMNwAcT
'HPLMNAcT': '6F62',
'HPLMNwAcT': '6F62',
'CPBCCH': '6F63',
'INVSCAN': '6F64',
'PNN': '6FC5',
'OPL': '6FC6',
'MBDN': '6FC7',
'EXT6': '6FC8',
'MBI': '6FC9',
'MWIS': '6FCA',
'CFIS': '6FCB',
'EXT7': '6FCC',
'SPDI': '6FCD',
'MMSN': '6FCE',
'EXT8': '6FCF',
'MMSICP': '6FD0',
'MMSUP': '6FD1',
'MMSUCP': '6FD2',
}
DF = {
'TELECOM': [MF_num, DF_num['TELECOM']],
'GSM': [MF_num, DF_num['GSM']],
'IS-41': [MF_num, DF_num['IS-41']],
'FP-CTS': [MF_num, DF_num['FP-CTS']],
'GRAPHICS': [MF_num, DF_num['GRAPHICS']],
'IRIDIUM': [MF_num, DF_num['IRIDIUM']],
'GLOBST': [MF_num, DF_num['GLOBST']],
'ICO': [MF_num, DF_num['ICO']],
'ACeS': [MF_num, DF_num['ACeS']],
'EIA/TIA-553': [MF_num, DF_num['EIA/TIA-553']],
'CTS': [MF_num, DF_num['CTS']],
'SoLSA': [MF_num, DF_num['SOLSA']],
'MExE': [MF_num, DF_num['MExE']],
}
EF = {
'ICCID': [MF_num, EF_num['ICCID']],
'ELP': [MF_num, EF_num['ELP']],
'ADN': DF['TELECOM']+[EF_num['ADN']],
'FDN': DF['TELECOM']+[EF_num['FDN']],
'SMS': DF['TELECOM']+[EF_num['SMS']],
'CCP': DF['TELECOM']+[EF_num['CCP']],
'MSISDN': DF['TELECOM']+[EF_num['MSISDN']],
'SMSP': DF['TELECOM']+[EF_num['SMSP']],
'SMSS': DF['TELECOM']+[EF_num['SMSS']],
'LND': DF['TELECOM']+[EF_num['LND']],
'SMSR': DF['TELECOM']+[EF_num['SMSR']],
'SDN': DF['TELECOM']+[EF_num['SDN']],
'EXT1': DF['TELECOM']+[EF_num['EXT1']],
'EXT2': DF['TELECOM']+[EF_num['EXT2']],
'EXT3': DF['TELECOM']+[EF_num['EXT3']],
'BDN': DF['TELECOM']+[EF_num['BDN']],
'EXT4': DF['TELECOM']+[EF_num['EXT4']],
'CMI': DF['TELECOM']+[EF_num['CMI']],
'ECCP': DF['TELECOM']+[EF_num['ECCP']],
'IMG': DF['GRAPHICS']+[EF_num['IMG']],
'SAI': DF['SoLSA']+[EF_num['SAI']],
'SLL': DF['SoLSA']+[EF_num['SLL']],
'MExE-ST': DF['MExE']+[EF_num['MExE-ST']],
'ORPK': DF['MExE']+[EF_num['ORPK']],
'ARPK': DF['MExE']+[EF_num['ARPK']],
'TPRPK': DF['MExE']+[EF_num['TPRPK']],
'LP': DF['GSM']+[EF_num['LP']],
'IMSI': DF['GSM']+[EF_num['IMSI']],
'Kc': DF['GSM']+[EF_num['Kc']],
'DCK': DF['GSM']+[EF_num['DCK']],
'PLMNsel': DF['GSM']+[EF_num['PLMNsel']],
'HPPLMN': DF['GSM']+[EF_num['HPPLMN']],
'CNL': DF['GSM']+[EF_num['CNL']],
'ACMmax': DF['GSM']+[EF_num['ACMmax']],
'SST': DF['GSM']+[EF_num['SST']],
'ACM': DF['GSM']+[EF_num['ACM']],
'GID1': DF['GSM']+[EF_num['GID1']],
'GID2': DF['GSM']+[EF_num['GID2']],
'PUCT': DF['GSM']+[EF_num['PUCT']],
'CBMI': DF['GSM']+[EF_num['CBMI']],
'SPN': DF['GSM']+[EF_num['SPN']],
'CBMID': DF['GSM']+[EF_num['CBMID']],
'BCCH': DF['GSM']+[EF_num['BCCH']],
'ACC': DF['GSM']+[EF_num['ACC']],
'FPLMN': DF['GSM']+[EF_num['FPLMN']],
'LOCI': DF['GSM']+[EF_num['LOCI']],
'AD': DF['GSM']+[EF_num['AD']],
'PHASE': DF['GSM']+[EF_num['PHASE']],
'VGCS': DF['GSM']+[EF_num['VGCS']],
'VGCSS': DF['GSM']+[EF_num['VGCSS']],
'VBS': DF['GSM']+[EF_num['VBS']],
'VBSS': DF['GSM']+[EF_num['VBSS']],
'eMLPP': DF['GSM']+[EF_num['eMLPP']],
'AAeM': DF['GSM']+[EF_num['AAeM']],
'ECC': DF['GSM']+[EF_num['ECC']],
'CBMIR': DF['GSM']+[EF_num['CBMIR']],
'NIA': DF['GSM']+[EF_num['NIA']],
'KcGPRS': DF['GSM']+[EF_num['KcGPRS']],
'LOCIGPRS': DF['GSM']+[EF_num['LOCIGPRS']],
'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
'HPLMNAcT': DF['GSM']+[EF_num['HPLMNAcT']],
'HPLMNwAcT': DF['GSM']+[EF_num['HPLMNAcT']],
'CPBCCH': DF['GSM']+[EF_num['CPBCCH']],
'INVSCAN': DF['GSM']+[EF_num['INVSCAN']],
'PNN': DF['GSM']+[EF_num['PNN']],
'OPL': DF['GSM']+[EF_num['OPL']],
'MBDN': DF['GSM']+[EF_num['MBDN']],
'EXT6': DF['GSM']+[EF_num['EXT6']],
'MBI': DF['GSM']+[EF_num['MBI']],
'MWIS': DF['GSM']+[EF_num['MWIS']],
'CFIS': DF['GSM']+[EF_num['CFIS']],
'EXT7': DF['GSM']+[EF_num['EXT7']],
'SPDI': DF['GSM']+[EF_num['SPDI']],
'MMSN': DF['GSM']+[EF_num['MMSN']],
'EXT8': DF['GSM']+[EF_num['EXT8']],
'MMSICP': DF['GSM']+[EF_num['MMSICP']],
'MMSUP': DF['GSM']+[EF_num['MMSUP']],
'MMSUCP': DF['GSM']+[EF_num['MMSUCP']],
}

View File

@@ -34,6 +34,12 @@ def h2i(s):
def i2h(s):
return ''.join(['%02x'%(x) for x in s])
def h2s(s):
return ''.join([chr((int(x,16)<<4)+int(y,16)) for x,y in zip(s[0::2], s[1::2]) if not (x == 'f' and y == 'f') ])
def s2h(s):
return b2h(s)
def swap_nibbles(s):
return ''.join([x+y for x,y in zip(s[1::2], s[0::2])])
@@ -42,3 +48,161 @@ 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
oe = len(imsi) & 1 # Odd (1) / Even (0)
ei = '%02x' % l + swap_nibbles('%01x%s' % ((oe<<3)|1, rpad(imsi, 15)))
return ei
def dec_imsi(ef):
"""Converts an EF value to the imsi string representation"""
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')
oe = (int(swapped[0])>>3) & 1 # Odd (1) / Even (0)
if not oe:
# if even, only half of last byte was used
l = l-1
if l != len(swapped) - 1:
return None
imsi = swapped[1:]
return imsi
def dec_iccid(ef):
return swap_nibbles(ef).strip('f')
def enc_iccid(iccid):
return swap_nibbles(rpad(iccid, 20))
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))
def dec_spn(ef):
byte1 = int(ef[0:2])
hplmn_disp = (byte1&0x01 == 0x01)
oplmn_disp = (byte1&0x02 == 0x02)
name = h2s(ef[2:])
return (name, hplmn_disp, oplmn_disp)
def enc_spn(name, hplmn_disp=False, oplmn_disp=False):
byte1 = 0x00
if hplmn_disp: byte1 = byte1|0x01
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
"""
from Crypto.Cipher import AES
from Crypto.Util.strxor import strxor
from pySim.utils import b2h
# We pass in hex string and now need to work on bytes
aes = AES.new(h2b(ki_hex))
opc_bytes = aes.encrypt(h2b(op_hex))
return b2h(strxor(opc_bytes, h2b(op_hex)))
def calculate_luhn(cc):
"""
Calculate Luhn checksum used in e.g. ICCID and IMEI
"""
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

76
pySim/utils_test.py Normal file
View File

@@ -0,0 +1,76 @@
#!/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()

View File

@@ -0,0 +1,6 @@
MCC=001
MNC=01
ICCID=1122334455667788990
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
IMSI=001010000000102

View File

@@ -0,0 +1,14 @@
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 !

View File

@@ -0,0 +1,7 @@
MCC=001
MNC=01
ICCID=1122334455667788990
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
IMSI=001010000000102
ADM=55538407

View File

@@ -0,0 +1,53 @@
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 !

View File

@@ -0,0 +1,7 @@
MCC=001
MNC=01
ICCID=1122334455667788990
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
IMSI=001010000000102
ADM=DDDDDDDD

View File

@@ -0,0 +1,14 @@
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 !

View File

@@ -0,0 +1,5 @@
MCC=001
MNC=01
IMSI=001010000000102
ADM=0123456789ABCDEF

View File

@@ -0,0 +1,6 @@
MCC=001
MNC=01
ICCID=1122334455667788990
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
IMSI=001010000000102

221
tests/pysim-test.sh Executable file
View File

@@ -0,0 +1,221 @@
#!/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

View File

@@ -0,0 +1,7 @@
MCC=001
MNC=01
ICCID=1122334455667788990
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
IMSI=001010000000102
ADM=12345678

View File

@@ -0,0 +1,7 @@
MCC=001
MNC=01
ICCID=1122334455667788990
KI=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
OPC=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
IMSI=001010000000102
ADM=DDDDDDDD