├── .gitignore
├── LICENSE
├── README.md
├── TODO
├── mim
├── __init__.py
├── mim_live.py
├── mim_live_live.py
├── mim_live_live_soft_sat.py
├── mim_live_soft_sat.py
├── mim_soft.py
├── mim_soft_live_auth.py
└── mim_soft_live_reg.py
├── res
├── live.gif
├── live.ico
├── live_live.gif
├── live_live_soft.gif
├── live_soft.gif
├── simLAB-logo.png
├── soft.gif
└── soft_live.gif
├── sim
├── __init__.py
├── file_parser.py
├── pyscard_rpc_ctrl.py
├── sim_backup.py
├── sim_card.py
├── sim_codes.py
├── sim_ctrl_2g.py
├── sim_ctrl_3g.py
├── sim_files.py
├── sim_files_2g.xml
├── sim_files_3g.xml
├── sim_reader.py
├── sim_router.py
└── sim_shell.py
├── sim_soft
├── __init__.py
├── sat_ctrl.py
├── sat_types.py
├── sim_auth.py
├── sim_backup.xml.bak
├── sim_soft.py
├── sim_soft_ctrl.py
└── sim_xml.py
├── simlab.py
├── tests
├── __init__.py
├── html_test_runner.py
├── live
│ ├── __init__.py
│ ├── test_debug.py
│ ├── test_shell.py
│ ├── test_sim.py
│ └── test_sim_2g.py
├── runner.py
└── soft
│ ├── __init__.py
│ ├── test_authentication.py
│ ├── test_ext_control_dbus.py
│ ├── test_ext_control_telnet.py
│ ├── test_sat.py
│ ├── test_shell.py
│ ├── test_sim.py
│ └── test_sim_2g.py
└── util
├── __init__.py
├── coder.py
├── dbus_ctrl.py
├── gsmtap.py
├── hextools.py
├── terminal_profile.py
├── types.py
└── types_g.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | mim_tmp.py
3 | sim_backup*.xml
4 | pyscard_rpc.log
5 | sim_soft.log
6 | apdu.log
7 | *.pyproj
8 | /tests/result
9 | \#*
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | simLAB
2 | =================
3 |
4 | **simLAB** is a pure python tool for editing and simulating SIM card. Together with simTrace HW, it allows modification of APDU exchanged between Terminal (Mobile Equipment) and SIM.
5 |
6 | Want to learn more? Detailed information can be found in the [simLAB wiki](https://github.com/kamwar/simLAB/wiki)
7 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | >> SIM SOFT <<
2 | # Implement SFI. Now as a workaround the FILE_NOT_FOUND status is returned
3 | # Implement security attributes (when EF_ARR is not set as access rule) for USIM
4 | # Implement millenge authentication for 3G
5 | # Implement sqn range check and EF_SQN update during dummy xor authentication
6 | # Add ADM4 tag inside fs xml file
7 | # Add support for 2G cards. Now only a few commands support this mode.
8 | # Move function related to logical channel inside LogicalChannel class
9 | # Implement opening logical channel with select command
10 | # Implement file linking (ADF0/EF_IMSI shall be linked with /7F20/EF_IMSI)
11 | # Implement INCREASE and DECREASE command
12 |
13 | >> SIM SOFT SAT <<
14 | # Display post action handler response without showing displayText in the meantime.
15 | Check for delayed proactive response
16 |
17 | >> COMMON <<
18 | # Number ADFs staring from 1 (e.g ADF1, ADF2 etc.)
19 | # Handle currentFile path for selecting files by path from MF
20 | (e.g. 00A40804067F105F3A4F3A)
21 |
22 | >> KNOWN ISSUE <<
23 | # ADF creation doesn't work for Gemalto v2.1 and Anritsu ISIM cards.
24 | INCORRECT_PARAMETER_IN_DATA_FIELD or INCORRECT_PARAMETERS_P1_P2 is returned.
25 | Many different combinations have been checked using:
26 | - Security Attribute Tag (8Bh)
27 | - PIN Status Template DO Tag (C6h)
28 | - DF_AID tag (84h) included in 'A5' tag
29 | # Gemalto card doesn't support RESIZE_FILE command.
30 | It supports EXTEND command instead. Removing a file record is impossible.
31 |
--------------------------------------------------------------------------------
/mim/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/mim/__init__.py
--------------------------------------------------------------------------------
/mim/mim_live.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2013 Tom Schouten
4 | # (c) 2014 Kamil Wartanowicz
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7 | import logging
8 |
9 | from sim import sim_router
10 | from sim import sim_card
11 | from sim import sim_reader
12 | from util import types
13 |
14 | logging.basicConfig(level=logging.INFO, format='%(message)s')
15 | simType=types.TYPE_USIM
16 |
17 | simCard = sim_card.SimCard()
18 | simCard.removeAllReaders()
19 | simCard.connect(sim_reader.READER_ID_0)
20 |
21 | simRouter = sim_router.SimRouter(cards=[simCard],
22 | atr=None,
23 | type=simType)
24 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE)
25 |
--------------------------------------------------------------------------------
/mim/mim_live_live.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7 | import logging
8 |
9 | from sim import sim_router
10 | from sim import sim_card
11 | from sim import sim_reader
12 | from util import hextools
13 | from util import types
14 |
15 | logging.basicConfig(level=logging.INFO, format='%(message)s')
16 |
17 | readers = sim_card.SimCard().listReaders()
18 | if "Dell" in readers[0]:
19 | readerFirst = sim_reader.READER_ID_0
20 | else:
21 | readerFirst = sim_reader.READER_ID_1
22 |
23 | simType=types.TYPE_USIM
24 |
25 | sim1 = sim_card.SimCard()
26 | sim1.removeAllReaders()
27 | sim1.connect(readerFirst)
28 | atr1 = sim1.getATR()
29 |
30 | sim2 = sim_card.SimCard()
31 | sim2.connect(not readerFirst)
32 | atr2 = sim2.getATR()
33 |
34 | simRouter = sim_router.SimRouter(cards=[sim1, sim2],
35 | atr=atr2, type=simType)
36 |
37 | sim2.routingAttr.filesReplaced = sim_card.FILES_REG
38 | sim2.routingAttr.insReplaced = ['INTERNAL_AUTHENTICATE']
39 | # Uncomment to forward SAT to sim2.
40 | #sim2.routingAttr.insReplaced.append(sim_card.SAT_INS)
41 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE)
--------------------------------------------------------------------------------
/mim/mim_live_live_soft_sat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7 | import logging
8 |
9 | from sim import sim_router
10 | from sim import sim_card
11 | from sim import sim_reader
12 | from util import types
13 |
14 | logging.basicConfig(level=logging.INFO, format='%(message)s')
15 |
16 | simType=types.TYPE_USIM
17 |
18 | sim1 = sim_card.SimCard()
19 | sim1.connect(sim_reader.READER_ID_0)
20 | atr1 = sim1.getATR()
21 |
22 | sim2 = sim_card.SimCard()
23 | sim2.connect(sim_reader.READER_ID_1)
24 | atr2 = sim2.getATR()
25 |
26 | sim3 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT)
27 | sim3.connect(sim_reader.READER_ID_0)
28 | atr3 = sim3.getATR()
29 |
30 | simRouter = sim_router.SimRouter(cards=[sim1, sim2, sim3],
31 | atr=atr1, type=simType)
32 |
33 | #sim2.routingAttr.filesReplaced = sim_card.FILES_REG
34 | #sim2.routingAttr.insReplaced = ['INTERNAL_AUTHENTICATE']
35 |
36 | # Forward SAT to sim3.
37 | sim3.routingAttr.insReplaced = sim_card.SAT_INS
38 |
39 | '''
40 | When SIM is invalidated, uncomment below.
41 | # Files which need to be the same on all sim cards.
42 | simRouter.getMainCard(0).routingAttr.filesReplaced += ['EF_ADN', 'EF_SDN', 'EF_SMS']
43 | # Uncomment when card resets after quering status. UE migth expect.
44 | # Different AID after SIM card switching if UE doesn't read EF_DIR on the basic channel.
45 | sim1.routingAttr.filesReplaced += ['EF_DIR']
46 | sim1.routingAttr.filesReplaced = ['STATUS']
47 | # Other SIM files which might cause problem if not the same on all cards.
48 | sim1.routingAttr.filesReplaced = +[
49 | 'EF_SMS', 'EF_MBDN', 'EF_EPSNSC', 'EF_MBI', 'EF_SMSS', 'EF_MWIS', 'EF_CFIS', 'EF_LI', 'EF_ELP',
50 | 'EF_UST', 'EF_EST', 'EF_AD', 'EF_NETPAR', 'EF_ACC', 'EF_HPPLMN', 'EF_HPLMNWACT', 'EF_PLMNWACT',
51 | 'EF_START_HFN', 'EF_THRESHOLD', 'EF_IMG', 'EF_ACM', 'EF_ACMMAX', 'EF_HIDDENKEY', 'EF_ICCID',
52 | 'EF_CBMI', 'EF_SMSP', 'EF_FDN', 'EF_ARR', 'EF_ANR', 'EF_MSISDN', 'EF_BDN', 'EF_ADN', 'EF_SDN',
53 | 'EF_OCI', 'EF_ICI', 'EF_MBDN', 'EF_EXT1', 'EF_EXT2', 'EF_EXT3' ]
54 | '''
55 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE)
--------------------------------------------------------------------------------
/mim/mim_live_soft_sat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7 | import logging
8 |
9 | from sim import sim_router
10 | from sim import sim_card
11 | from sim import sim_reader
12 | from util import types
13 |
14 | logging.basicConfig(level=logging.INFO, format='%(message)s')
15 |
16 | simType=types.TYPE_USIM
17 |
18 | sim1 = sim_card.SimCard()
19 | sim1.connect(sim_reader.READER_ID_0)
20 | atr1 = sim1.getATR()
21 |
22 | sim2 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT)
23 | sim2.connect(sim_reader.READER_ID_0)
24 | atr2 = sim2.getATR()
25 |
26 | simRouter = sim_router.SimRouter(cards=[sim1, sim2],
27 | atr=atr1, type=simType)
28 |
29 | # Forward SAT to sim2.
30 | sim2.routingAttr.insReplaced = sim_card.SAT_INS
31 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE)
--------------------------------------------------------------------------------
/mim/mim_soft.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7 | import logging
8 |
9 | from sim import sim_router
10 | from sim import sim_card
11 | from sim import sim_reader
12 | from util import types
13 |
14 | logging.basicConfig(level=logging.INFO, format='%(message)s')
15 |
16 | simType=types.TYPE_USIM
17 |
18 | simCard = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType)
19 | simCard.removeAllReaders()
20 | simCard.connect(sim_reader.READER_ID_0)
21 |
22 | simRouter = sim_router.SimRouter(cards=[simCard],
23 | atr=None,
24 | type=simType)
25 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE)
26 |
--------------------------------------------------------------------------------
/mim/mim_soft_live_auth.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7 | import logging
8 |
9 | from sim import sim_router
10 | from sim import sim_card
11 | from sim import sim_reader
12 | from sim import sim_shell
13 | from sim import sim_ctrl_3g
14 | from util import types
15 |
16 | simType=types.TYPE_USIM
17 | CONNECT_EXTERNAL_SERVER = False
18 | SERVER_IP = "10.28.27.200"
19 |
20 | if CONNECT_EXTERNAL_SERVER:
21 | # Connect to external SIM server/reader.
22 | sim_reader.PYSCARD_RPC_IP_ADDRESS = SERVER_IP
23 | sim_reader.LOCAL_PYSCARD_SERVER = False
24 | else:
25 | sim_reader.LOCAL_PYSCARD_SERVER = True # Use local reader
26 | logging.basicConfig(level=logging.INFO, format='%(message)s')
27 |
28 | sim1 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType)
29 | sim1.connect(sim_reader.READER_ID_0)
30 | atr1 = sim1.getATR()
31 |
32 | sim2 = sim_card.SimCard(type=simType)
33 | sim2.connect(sim_reader.READER_ID_0)
34 | atr2 = sim2.getATR()
35 |
36 | simRouter = sim_router.SimRouter(cards=[sim1, sim2],
37 | atr=atr1, type=simType)
38 |
39 | simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED)
40 | shell = simRouter.shell
41 |
42 | #simRouter.copyFiles(sim2, sim1, sim_card.FILES_REG)
43 | simRouter.copyFiles(sim2, sim1, ['EF_IMSI', 'EF_LOCI', 'EF_SMSP'])
44 |
45 | # Select ADF_USIM on the main channel of SIM1.
46 | # It's needed for authenticate instruction.
47 | shell.select_sim_card("1")
48 | shell.set_active_channel("0")
49 | shell.cd("/ADF_USIM")
50 | shell.set_active_channel("1")
51 | shell.select_sim_card("0")
52 |
53 | sim1.routingAttr.insReplaced = sim_card.SAT_INS
54 | # Only INTERNAL_AUTHENTICATE instruction is forwarded to sim2.
55 | sim2.routingAttr.insCommon = []
56 | sim2.routingAttr.filesCommon = []
57 | sim2.routingAttr.filesReplaced = []
58 | sim2.routingAttr.insReplaced = ['INTERNAL_AUTHENTICATE']
59 | #simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE)
60 | simRouter.run(mode=sim_router.ROUTER_MODE_TELNET)
61 |
--------------------------------------------------------------------------------
/mim/mim_soft_live_reg.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7 | import logging
8 |
9 | from sim import sim_router
10 | from sim import sim_card
11 | from sim import sim_reader
12 | from util import types
13 |
14 | logging.basicConfig(level=logging.INFO, format='%(message)s')
15 |
16 | simType=types.TYPE_USIM
17 |
18 | sim1 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType)
19 | sim1.connect(sim_reader.READER_ID_0)
20 | atr1 = sim1.getATR()
21 |
22 | sim2 = sim_card.SimCard()
23 | sim2.connect(sim_reader.READER_ID_0)
24 | atr2 = sim2.getATR()
25 |
26 | simRouter = sim_router.SimRouter(cards=[sim1, sim2],
27 | atr=atr1, type=simType)
28 |
29 | sim1.routingAttr.insReplaced = sim_card.SAT_INS
30 | sim2.routingAttr.filesReplaced = sim_card.FILES_REG
31 | sim2.routingAttr.insReplaced = ['INTERNAL_AUTHENTICATE']
32 | simRouter.run(mode=sim_router.ROUTER_MODE_INTERACTIVE)
--------------------------------------------------------------------------------
/res/live.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live.gif
--------------------------------------------------------------------------------
/res/live.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live.ico
--------------------------------------------------------------------------------
/res/live_live.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live_live.gif
--------------------------------------------------------------------------------
/res/live_live_soft.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live_live_soft.gif
--------------------------------------------------------------------------------
/res/live_soft.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/live_soft.gif
--------------------------------------------------------------------------------
/res/simLAB-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/simLAB-logo.png
--------------------------------------------------------------------------------
/res/soft.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/soft.gif
--------------------------------------------------------------------------------
/res/soft_live.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/res/soft_live.gif
--------------------------------------------------------------------------------
/sim/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/sim/__init__.py
--------------------------------------------------------------------------------
/sim/file_parser.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2015, Kamil Wartanowicz
4 | from util import hextools
5 | from util import types
6 | from util import coder
7 |
8 | MODE_GET = 0
9 | MODE_SET = 1
10 |
11 | class FileParser(object):
12 | def __init__(self):
13 | pass
14 |
15 | def getEfImsi(self, data):
16 | data = hextools.hex2bytes(data)
17 | imsi = hextools.decode_BCD(data)[3:]
18 | return imsi
19 |
20 | def setEfImsi(self, imsi):
21 | if len(imsi) % 2:
22 | dataLow = 9
23 | else:
24 | dataLow = 1
25 | firstByte = dataLow<<4 | int(imsi[0], 16)
26 | imsi = "%02X%s" %(firstByte, imsi[1:])
27 | imsi = hextools.encode_BCD(imsi)
28 | return "08%s" %hextools.bytes2hex(imsi)
29 |
30 | def getEfUst(self, data):
31 | data = hextools.hex2bytes(data)
32 | ustTable = []
33 | for byte in data:
34 | for bitId in range(8):
35 | value = int(byte & (1 << bitId) != 0)
36 | ustTable.append(value)
37 | return ustTable
38 |
39 | def setEfUst(self, data):
40 | ustTable = data
41 | ustRaw = []
42 | for i, bitValue in enumerate(ustTable):
43 | byteId = i / 8
44 | bitId = i % 8
45 | if len(ustRaw) <= byteId:
46 | ustRaw.append(0x00)
47 | if bitValue:
48 | ustRaw[byteId] |= (1 << bitId)
49 | else:
50 | ustRaw[byteId] &= ~(1 << bitId)
51 | return hextools.bytes2hex(ustRaw)
52 |
53 | def getEfEcc(self, data):
54 | eccStr = ''
55 | data = data.split(';')
56 | for record in data:
57 | recordRaw = hextools.hex2bytes(record)
58 | number = hextools.decode_BCD(recordRaw[0:3])
59 | number = number.replace('F','')
60 | if not number:
61 | continue
62 | #number = type.removeTrailingBytes(hextools.hex2bytes(number), 0xFF)
63 | aplha = None
64 | category = recordRaw[-1]
65 | eccStr += "number=%s,alpha=%s,cat=%d;" %(number, aplha, category)
66 | eccStr = eccStr.rstrip(";")
67 | if not eccStr:
68 | eccStr = "EMPTY"
69 | return eccStr
70 |
71 | def setEfEcc(self, data):
72 | data = data.split(';')
73 | dataNew = ''
74 | for record in data:
75 | number = types.getParamValue(record, "number")
76 | if not number:
77 | return False
78 | category = types.getParamValue(record, "cat")
79 | number = number.replace('F','')
80 | if number:
81 | numberBcd = hextools.encode_BCD(number)
82 | numberBcd = hextools.bytes2hex(numberBcd)
83 | numberBcd = types.addTrailingBytes(numberBcd, 0xFF, 3)
84 | else:
85 | numberBcd = "FFFFFF"
86 | dataTmp = numberBcd
87 | if category:
88 | raise Exception("Not implemented")
89 | #TODO: move it to the last byte
90 | dataTmp += "%02X" %int(category)
91 | if dataNew:
92 | dataNew = "%s;%s" %(dataNew, dataTmp)
93 | else:
94 | dataNew = dataTmp
95 | return dataNew
96 |
97 | def getEfSpn(self, data):
98 | spnRaw = types.removeTrailingBytes(hextools.hex2bytes(data), 0xFF)
99 | if spnRaw:
100 | displayByte = spnRaw[0]
101 | name = hextools.bytes2hex(spnRaw[1:]).decode("hex")
102 | else:
103 | return ''
104 | return "name=%s,display=%02X" %(name, displayByte)
105 |
106 | def setEfSpn(self, value):
107 | name = types.getParamValue(value, "name")
108 | if not name:
109 | raise Exception("Name not provided")
110 | displayByte = types.getParamValue(value, "display")
111 | if displayByte:
112 | displayByte = int(displayByte)
113 | else:
114 | #display is not provided, set default value
115 | displayByte = 0x00
116 | value = "%02X%s" %(displayByte, name.encode('hex'))
117 | return value
118 |
119 | def getEfCphs_onstr(self, data):
120 | nameHex = types.removeTrailingBytes(hextools.hex2bytes(data), 0xFF)
121 | #fullName = coder.decodeGsm7(hextools.bytes2hex(nameHex))
122 | #return fullName
123 | return hextools.bytes2hex(nameHex.decode("hex"))
124 |
125 | def setEfCphs_onstr(self, name):
126 | #return coder.encodeGsm7(data)
127 | return name.encode('hex')
128 |
129 | def getEfImpu(self, data):
130 | impuStr = ''
131 | data = data.split(';')
132 | for record in data:
133 | value = types.removeTrailingBytes(hextools.hex2bytes(record), 0xFF)
134 | if len(value) < 3:
135 | continue
136 | if impuStr:
137 | impuStr += ";"
138 | impuStr += hextools.bytes2hex(value[2:]).decode("hex")
139 | return impuStr
140 |
141 | def setEfImpu(self, data):
142 | data = data.split(';')
143 | dataNew = ''
144 | for record in data:
145 | tag = 0x80
146 | length = len(record)
147 | value = "%02X%02X%s" %(tag, length, record.encode('hex'))
148 | if dataNew:
149 | dataNew += ";"
150 | dataNew += value
151 | return dataNew
152 |
153 | def getEfImpi(self, data):
154 | valueRaw = types.removeTrailingBytes(hextools.hex2bytes(data), 0xFF)
155 | if len(valueRaw) < 3:
156 | return ''
157 | value = hextools.bytes2hex(valueRaw[2:]).decode("hex")
158 | return "impi=%s" %value
159 |
160 | def setEfImpi(self, data):
161 | impi = types.getParamValue(data, "impi")
162 | if not impi:
163 | raise Exception("impi not provided")
164 | tag = 0x80
165 | length = len(impi)
166 | value = "%02X%02X%s" %(tag, length, impi.encode('hex'))
167 | return value
168 |
169 | def getEfPcscf(self, data):
170 | #TODO: handle many records
171 | firstRecord = data.split(';')[0]
172 | value = types.removeTrailingBytes(hextools.hex2bytes(firstRecord), 0xFF)
173 | if len(value) < 3:
174 | return ''
175 | cscf = hextools.bytes2hex(value[3:]).decode("hex")
176 | return "cscf=%s" %cscf
177 |
178 | def setEfPcscf(self, data):
179 | data = data.split(';')
180 | dataNew = ''
181 | for record in data:
182 | cscf = types.getParamValue(record, "cscf")
183 | if not cscf:
184 | raise Exception("cscf not provided")
185 | tag = 0x80
186 | length = len(cscf) + 1
187 | '''
188 | Value | Name
189 | ============
190 | '00' | FQDN
191 | '01' | IPv4
192 | '02' | IPv6
193 | '''
194 | addrType = 0x00
195 | value = "%02X%02X%02X%s" %(tag, length, addrType, cscf.encode('hex'))
196 | if dataNew:
197 | dataNew += ";"
198 | dataNew += value
199 | return dataNew
200 |
201 | def getEfLoci(self, data):
202 | valueRaw = hextools.hex2bytes(data)
203 | tmsi = hextools.bytes2hex(valueRaw[0:4])
204 | lai = hextools.bytes2hex(valueRaw[4:9])
205 | #TODO: check for mnc containing 3digits
206 | mcc_mnc = hextools.decode_BCD(hextools.hex2bytes(lai)[0:3])
207 | lac = lai[6:10]
208 | rfu = hextools.bytes2hex([valueRaw[9]])
209 | loction_status = hextools.bytes2hex([valueRaw[10]])
210 | '''
211 | loction_status
212 | Bits: b3 b2 b1
213 | 0 0 0 : updated.
214 | 0 0 1 : not updated.
215 | 0 1 0 : PLMN not allowed.
216 | 0 1 1 : Location Area not allowed.
217 | 1 1 1 : reserved
218 | '''
219 | return "tmsi=%s,mcc_mnc=%s,lac=%s,loc_status=%s"\
220 | %(tmsi, mcc_mnc, lac, loction_status)
221 |
222 | def setEfLoci(self, data):
223 | param = "tmsi"
224 | tmsi = types.getParamValue(data, param)
225 | if not tmsi:
226 | raise Exception("%s not provided" %param)
227 |
228 | param = "lai"
229 | lai = types.getParamValue(data, param)
230 | if not lai:
231 | #if lai is not provided, check mcc_mnc and lac
232 | param = "mcc_mnc"
233 | mccMnc = types.getParamValue(data, param)
234 | if not mccMnc:
235 | raise Exception("%s not provided" %param)
236 | if len(mccMnc) != 6:
237 | mnc3 = 'F'
238 | else:
239 | mnc3 = mccMnc[5]
240 | mccMnc = "%s%s%s" %(mccMnc[0:3], mnc3, mccMnc[3:5])
241 |
242 | param = "lac"
243 | lac = types.getParamValue(data, param)
244 | if not lac:
245 | raise Exception("%s not provided" %param)
246 | lai = "%s%04X" %(hextools.bytes2hex(hextools.encode_BCD(mccMnc)), int(lac, 16))
247 | param = "rfu"
248 | rfu = types.getParamValue(data, param)
249 | if not rfu:
250 | rfu = "FF"
251 |
252 | param = "loc_status"
253 | locStatus = types.getParamValue(data, param)
254 | if not locStatus:
255 | locStatus = "00"
256 |
257 | loci = "%s%s%s%s" %(tmsi, lai, rfu, locStatus)
258 | return loci
259 |
260 | def getEfPnn(self, data):
261 | data = data.split(';')
262 | dataNew = ''
263 | for record in data:
264 | if not record:
265 | continue
266 | binRecord = hextools.hex2bytes(record)
267 | if binRecord.count(0xFF) == len(binRecord):
268 | continue
269 | fullNameRaw = types.parseTlv(binRecord, types.FULL_NW_NAME_TAG)
270 | shortNameRaw = types.parseTlv(binRecord, types.SHORT_NW_NAME_TAG)
271 | additionalInfo = types.parseTlv(binRecord, types.ADDITIONAL_INFORMATION_PLMN_TAG)
272 | if fullNameRaw:
273 | infoByte = fullNameRaw[0]
274 | spareBits = infoByte & 0b00000111
275 | ci = infoByte >> 3 & 0b00000001
276 | dcs = infoByte >> 4 & 0b00000111
277 | if dcs != 0x00:
278 | raise Exception("Only GSM coding is supported in DCS, current coding: %02X" %dcs)
279 | fullName = coder.decodeGsm7(hextools.bytes2hex(fullNameRaw[1:]))
280 | else:
281 | fullName = None
282 | if shortNameRaw:
283 | infoByte = shortNameRaw[0]
284 | spareBits = infoByte & 0b00000111
285 | ci = infoByte >> 3 & 0b00000001
286 | dcs = infoByte >> 4 & 0b00000111
287 | if dcs != 0x00:
288 | raise Exception("Only GSM coding is supported in DCS, current coding: %02X" %dcs)
289 | shortName = coder.decodeGsm7(hextools.bytes2hex(shortNameRaw[1:]))
290 | else:
291 | shortName = None
292 | dataNew += "full_name=%s,short_name=%s"\
293 | %(fullName, shortName)
294 | if additionalInfo:
295 | dataNew += ",additional_info=%s" %additionalInfo
296 | dataNew += ";"
297 | return dataNew
298 |
299 | def setEfPnn(self, data):
300 | data = data.split(';')
301 | dataNew = ''
302 | for record in data:
303 | if not record:
304 | continue
305 | tmpData = []
306 | infoFull = types.getParamValue(record, "info_full")
307 | fullName = types.getParamValue(record, "full_name")
308 | infoShort = types.getParamValue(record, "info_short")
309 | shortName = types.getParamValue(record, "short_name")
310 | additionalInfo = types.getParamValue(record, "additional_info")
311 | if fullName:
312 | fullNameGsm7 = coder.encodeGsm7(fullName)
313 | if infoFull:
314 | infoByte = int(infoFull, 16)
315 | spareBits = infoByte & 0b00000111
316 | ci = infoByte >> 3 & 0b00000001
317 | dcs = infoByte >> 4 & 0b00000111
318 | if dcs != 0x00:
319 | raise Exception("Only GSM coding is supported in DCS")
320 | ext = infoByte >> 7 & 0b00000001
321 | else:
322 | spareBits = len(fullName) % 8
323 | ci = 0 #don't add the letters for the Country's Initials
324 | dcs = 0 #GSM
325 | ext = 1 #?
326 | infoByte = ext << 7 | dcs << 3 | ci << 2 | spareBits
327 | fullNameGsm7 = "%02X%s" %(infoByte, fullNameGsm7)
328 | types.addTlv(tmpData, types.FULL_NW_NAME_TAG, hextools.hex2bytes(fullNameGsm7))
329 | if shortName:
330 | shortNameGsm7 = coder.encodeGsm7(shortName)
331 | if infoShort:
332 | infoByte = int(infoShort, 16)
333 | spareBits = infoByte & 0b00000111
334 | ci = infoByte >> 3 & 0b00000001
335 | dcs = infoByte >> 4 & 0b00000111
336 | if dcs != 0x00:
337 | raise Exception("Only GSM coding is supported in DCS")
338 | ext = infoByte >> 7 & 0b00000001
339 | else:
340 | spareBits = len(shortName) % 8
341 | ci = 0 #don't add the letters for the Country's Initials
342 | dcs = 0 #GSM
343 | ext = 1 #?
344 | infoByte = ext << 7 | dcs << 3 | ci << 2 | spareBits
345 | shortNameGsm7 = "%02X%s" %(infoByte, shortNameGsm7)
346 | types.addTlv(tmpData, types.SHORT_NW_NAME_TAG, hextools.hex2bytes(shortNameGsm7))
347 | if additionalInfo:
348 | types.addTlv(tmpData, types.ADDITIONAL_INFORMATION_PLMN_TAG, hextools.hex2bytes(additionalInfo))
349 | dataNew += "%s;" %hextools.bytes2hex(tmpData)
350 | return dataNew
351 |
352 | def getEfOpl(self, data):
353 | data = data.split(';')
354 | dataNew = ''
355 | for record in data:
356 | if not record:
357 | continue
358 | binRecord = hextools.hex2bytes(record)
359 | if binRecord.count(0xFF) == len(binRecord):
360 | continue
361 | lai = hextools.bytes2hex(binRecord[0:7])
362 | mccMnc = hextools.decode_BCD(hextools.hex2bytes(lai)[0:3])
363 | lacStart = lai[6:10]
364 | lacEnd = lai[10:14]
365 | lacRange = "%s-%s" %(lacStart, lacEnd)
366 | pnnId = binRecord[7]
367 | dataNew += "mcc_mnc=%s,lac=%s,pnnId=%d;" %(mccMnc, lacRange, pnnId)
368 | return dataNew
369 |
370 | def setEfOpl(self, data):
371 | data = data.split(';')
372 | dataNew = ''
373 | for record in data:
374 | if not record:
375 | continue
376 | mccMnc = types.getParamValue(record, "mcc_mnc")
377 | if not mccMnc:
378 | raise Exception("mcc_mnc not provided")
379 | if len(mccMnc) != 6:
380 | mnc3 = 'F'
381 | else:
382 | mnc3 = mccMnc[5]
383 | mccMnc = "%s%s%s" %(mccMnc[0:3], mnc3, mccMnc[3:5])
384 | lacRange = types.getParamValue(record, "lac")
385 | if not lacRange:
386 | lacRange = '0000-FFFE'
387 | lacStart = lacRange.split("-")[0]
388 | lacEnd = lacRange.split("-")[1]
389 | pnnId = int(types.getParamValue(record, "pnnId"), 16)
390 | lai = "%s%04X%04X" %(hextools.bytes2hex(hextools.encode_BCD(mccMnc)), int(lacStart, 16), int(lacEnd, 16))
391 | dataNew += "%s%02X;" %(lai, pnnId)
392 | return dataNew
393 |
394 | def fileHandler(self, mode, fileName, data):
395 | fileName = fileName.replace("EF_", '')
396 | #remove forbidden characters in function name
397 | fileName = fileName.replace("-", '')
398 | fileName = fileName.lower()
399 | fileName = "%s%s" %(fileName[0].upper(), fileName[1:])
400 | if mode == MODE_GET:
401 | prefix = "get"
402 | else:
403 | prefix = "set"
404 | functionName = "%sEf%s" %(prefix, fileName)
405 | try:
406 | handler = getattr(self, functionName)
407 | except:
408 | raise Exception("Add method '%s' in file %s" %(functionName, __file__))
409 | return handler(data)
410 |
411 | def getFileValue(self, fileName, data):
412 | return self.fileHandler(MODE_GET, fileName, data)
413 |
414 | def setFileValue(self, fileName, data):
415 | return self.fileHandler(MODE_SET, fileName, data)
416 |
--------------------------------------------------------------------------------
/sim/pyscard_rpc_ctrl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 | import sys,os.path
5 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
6 | import logging
7 | import inspect
8 | from optparse import OptionParser
9 | import os
10 | import shlex
11 | import smartcard
12 | from smartcard.Exceptions import CardConnectionException
13 | import subprocess
14 | import threading
15 | import time
16 | import zerorpc
17 | import zmq
18 | from util import hextools
19 |
20 | class PyscardRPC(object):
21 | ####################
22 | # local methods
23 | ####################
24 | def __init__(self, logLevel=logging.WARNING):
25 | self.setupLogger(logLevel)
26 | self.readers = []
27 | self.pollSim = {}
28 |
29 | def setupLogger(self, logLevel):
30 | logger = logging.getLogger()
31 | logger.setLevel(logging.INFO)
32 | consoleHndl = logging.StreamHandler()
33 | FORMATTER = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S')
34 | consoleHndl.setFormatter(FORMATTER)
35 | consoleHndl.setLevel(logLevel)
36 | logger.addHandler(consoleHndl)
37 |
38 | def runPollSim(self, index):
39 | if index in self.pollSim.keys():
40 | # Poll already started.
41 | return
42 | pollSim = {index : PollSimThread(self, index)}
43 | pollSim[index].setDaemon(True)
44 | pollSim[index].update()
45 | pollSim[index].start()
46 | self.pollSim.update(pollSim)
47 |
48 | def stopPollSim(self, index):
49 | if index not in self.pollSim.keys():
50 | # Poll not started.
51 | return
52 | self.pollSim[index].stop()
53 |
54 | def updatePoll(self, index):
55 | if index in self.pollSim.keys():
56 | self.pollSim[index].update()
57 |
58 | # TODO: remove, already implemented in PyscardRpcServerThread.
59 | # Endpoint is e.g. "tcp://0.0.0.0:4242"
60 | def runServer(self, endpoint):
61 | self.s = zerorpc.Server(self)
62 | #self.s.bind(endpoint)
63 | try:
64 | self.s.bind(endpoint)
65 | except zmq.ZMQError as e:
66 | if e.errno == zmq.EADDRINUSE:
67 | logging.info("Pyscard RPC server already running\n")
68 | else:
69 | logging.warning("Pyscard RPC server " + endpoint + " could not be started")
70 | self.s.close()
71 | sys.exit(0)
72 |
73 | #create result file only when server is started
74 | dir = os.path.dirname(__file__)
75 | resultFile = dir + "/../pyscard_rpc.log"
76 | FORMATTER = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S')
77 | fileHndl = logging.FileHandler(resultFile, mode='w')
78 | fileHndl.setFormatter(FORMATTER)
79 | fileHndl.setLevel(logging.DEBUG)
80 |
81 | logger = logging.getLogger()
82 | logger.addHandler(fileHndl)
83 |
84 | logging.info("Pyscard RPC Server " + endpoint + " started\n")
85 |
86 | self.s.run()
87 | return self
88 |
89 | def getReader(self, index):
90 | for reader in self.readers:
91 | if reader.index == index:
92 | return reader
93 | return None
94 |
95 | def getReaderName(self, index):
96 | reader = self.getReader(index)
97 | if not reader:
98 | return None
99 | return reader.reader.name
100 |
101 | def getCard(self, index):
102 | self.checkReader(index)
103 | return self.getReader(index).card
104 |
105 | def checkReader(self, index):
106 | if not self.getReader(index):
107 | raise Exception("Reader with index=%d not created" %index)
108 |
109 | def checkCard(self, index):
110 | if not self.getCard(index):
111 | raise Exception("Card for reader with index=%d not created" %index)
112 |
113 | ####################
114 | # remote methods
115 | ####################
116 | def listReaders(self):
117 | logFunctionAndArgs()
118 | readers = smartcard.System.readers()
119 | readersStr = []
120 | for reader in readers:
121 | readersStr.append(reader.name)
122 | logReturnVal(readersStr=readersStr)
123 | return readersStr
124 |
125 | def addReader(self, index):
126 | logFunctionAndArgs()
127 | readersConnected = smartcard.System.readers()
128 | if not len(readersConnected):
129 | raise Exception("No reader connected")
130 | if index >= len(readersConnected):
131 | raise Exception("Reader id:%d not connected, number of connected readers:%d"
132 | %(index, len(readersConnected)))
133 | newReader = False
134 | if not self.getReader(index):
135 | newReader = True
136 | self.readers.append(Reader())
137 | self.readers[-1].index = index
138 | self.readers[-1].reader = readersConnected[index]
139 | logReturnVal(newReader=newReader)
140 | return newReader
141 |
142 | def removeReader(self, index):
143 | logFunctionAndArgs()
144 | self.checkReader(index)
145 | for reader in self.readers:
146 | if reader.index == index:
147 | del reader
148 | logReturnVal()
149 | return None
150 |
151 | def removeAllReaders(self):
152 | logFunctionAndArgs()
153 | for reader in self.readers:
154 | del reader
155 | logReturnVal()
156 | return None
157 |
158 | def r_createConnection(self, index):
159 | logFunctionAndArgs()
160 | newConnection = False
161 | self.checkReader(index)
162 | if not self.getReader(index).card:
163 | self.getReader(index).card = self.getReader(index).reader.createConnection()
164 | newConnection = True
165 | logReturnVal(newConnection=newConnection)
166 | return newConnection
167 |
168 | def c_connect(self, index):
169 | logFunctionAndArgs()
170 | self.checkCard(index)
171 | self.getCard(index).connect()
172 | # TODO: get simType from sim_router.
173 | self.runPollSim(index)
174 | logReturnVal()
175 | return None
176 |
177 | def c_disconnect(self, index):
178 | logFunctionAndArgs()
179 | self.checkCard(index)
180 | self.stopPollSim(index)
181 | self.getCard(index).disconnect()
182 | logReturnVal()
183 | return None
184 |
185 | def c_control(self, controlCode, inBuffer, index):
186 | #logFunctionAndArgs()
187 | self.checkCard(index)
188 | self.getCard(index).control(controlCode, inBuffer)
189 | #logReturnVal()
190 | return None
191 |
192 | def c_getATR(self, index):
193 | logFunctionAndArgs()
194 | self.checkCard(index)
195 | self.updatePoll(index)
196 | try:
197 | atr = self.getCard(index).getATR()
198 | except CardConnectionException:
199 | time.sleep(0.1)
200 | atr = self.getCard(index).getATR()
201 | logReturnVal(atr=atr)
202 | return atr
203 |
204 | def c_transmit(self, apdu, index):
205 | if apdu not in POLL_STATUS_PATTERN_BIN:
206 | logFunctionAndArgs()
207 | self.checkCard(index)
208 | self.updatePoll(index)
209 | data, sw1, sw2 = self.getCard(index).transmit(apdu)
210 | if apdu not in POLL_STATUS_PATTERN_BIN:
211 | logReturnVal(data=data, sw1=sw1, sw2=sw2)
212 | return data, sw1, sw2
213 |
214 | class Reader(object):
215 | def __init__(self):
216 | self.index = None
217 | self.reader = None
218 | self.card = None
219 |
220 | POLL_STATUS_PATTERN_BIN = [
221 | [0x80, 0xF2, 0x00, 0x0C, 0x00], #3G SIM
222 | [0xA0, 0xF2, 0x00, 0x00, 0x01], #2G SIM
223 | ]
224 |
225 | def logFunctionAndArgs():
226 | frame = inspect.getouterframes(inspect.currentframe())[1][0]
227 | args, _, _, values = inspect.getargvalues(frame)
228 | frameinfo = inspect.getframeinfo(frame)
229 | functionName=inspect.getframeinfo(frame)[2]
230 | output = ""
231 | for arg in args[1:]: #[1:] skip the first argument 'self'
232 | value = values[arg]
233 | if isinstance(value, str):
234 | #add apostrophes for string values
235 | value = "\'"+value+"\'"
236 | elif isinstance(value, int):
237 | value = ''.join('%02X' % value)
238 | else:
239 | newValue = ""
240 | for i in value:
241 | if isinstance(i, int):
242 | newValue += '%02X' % i
243 | else:
244 | newValue += str(i)
245 | value = newValue
246 | output += arg + '=' + value
247 | if arg != args[-1]:
248 | #add comma if not the last element
249 | output +=','
250 | #do not print "\n' as a new line
251 | output = output.replace("\n","\\n")
252 | logging.info("--> "+functionName+'('+output+')')
253 |
254 | def logReturnVal(**kwargs):
255 | output = ""
256 | for key, value in kwargs.iteritems():
257 | if isinstance(value, str):
258 | #add apostrophes for string values
259 | value = "\'"+value+"\'"
260 | elif isinstance(value, int):
261 | value = ''.join('%02X' % value)
262 | else:
263 | newValue = ""
264 | for i in value:
265 | if isinstance(i, int):
266 | newValue += '%02X' % i
267 | else:
268 | newValue += str(i)
269 | value = newValue
270 | output += key + ':' + value + ', '
271 | output = output.rstrip(', ') #remove last comma and space
272 | logging.info("<-- "+output+'\n')
273 |
274 | ######################
275 | #Server configuration#
276 | ######################
277 | class PyscardRpcServerThread(threading.Thread):
278 | def __init__(self, port, logLevel=logging.WARNING):
279 | threading.Thread.__init__(self)
280 | self.port = port
281 | self.logLevel = logLevel
282 | threading.Thread.setName(self, 'PyscardRpcServerThread')
283 | self.proc = None
284 | self.__lock = threading.Lock()
285 |
286 | def run(self):
287 | self.__lock.acquire();
288 | #start locally pyscard RPC server
289 | rpcServerScript = os.path.abspath(__file__).replace("\\", "/")
290 | scriptCmd = "python %s --port=%d --logLevel=%d" %(rpcServerScript, self.port, self.logLevel)
291 | self.proc = subprocess.Popen(shlex.split(scriptCmd))
292 | self.__lock.release();
293 |
294 | def close(self):
295 | if self.proc:
296 | subprocess.Popen.terminate(self.proc)
297 |
298 | class PollSimThread(threading.Thread):
299 | def __init__(self, pyscardRpc, index, pollRate=400):
300 | threading.Thread.__init__(self)
301 | self.pyscardRpc = pyscardRpc
302 | self.index = index
303 | self.pollRate = pollRate
304 | self.poll = False
305 | self.startTime = 0
306 | self.lastUpdate = 0
307 | self.pattern = 0
308 | threading.Thread.setName(self, 'PollSimThread')
309 | self.__lock = threading.Lock()
310 |
311 | def run(self):
312 | self.__lock.acquire()
313 | self.poll = True
314 |
315 | while (self.poll):
316 | self.startTime = time.time()
317 | if self.startTime - self.lastUpdate > (self.pollRate / 1000.0 - 0.1):
318 | try:
319 | sw1 = self.pyscardRpc.c_transmit(hextools.hex2bytes(POLL_STATUS_PATTERN[self.pattern]),
320 | self.index)[1]
321 | if sw1 != 0x90 and self.pattern < (len(POLL_STATUS_PATTERN) - 1):
322 | # Use different pattern e.g. 2G status.
323 | self.pattern += 1
324 | except:
325 | logging.error("Stop polling")
326 | self.stop()
327 | self.lastUpdate = time.time()
328 | time.sleep(self.pollRate / 1000.0)
329 | self.__lock.release()
330 |
331 | def update(self):
332 | self.lastUpdate = time.time() - 0.1
333 |
334 | def stop(self):
335 | self.poll = False
336 | try:
337 | self.join()
338 | except: pass
339 |
340 | POLL_STATUS_PATTERN = [
341 | "80F2000C00", #3G SIM
342 | "A0F2000001", #2G SIM
343 | ]
344 |
345 | if __name__ == '__main__':
346 | parser = OptionParser()
347 | parser.add_option("-p", "--port", dest="port", help="Port number")
348 | parser.add_option("-v", "--logLevel", dest="logLevel", help="Log level")
349 | (options, args) = parser.parse_args()
350 | if options.port:
351 | try:
352 | pyscardRpcPort = int(options.port)
353 | except:
354 | raise Exception("Expecting --port argument to be integer, got %r instead" %options.port)
355 | pyscardRpcPort = options.port
356 | else:
357 | pyscardRpcPort = 4148
358 | if options.logLevel:
359 | logLevel = int(options.logLevel)
360 | else:
361 | logLevel = logging.INFO
362 | PyscardRPC(logLevel).runServer("tcp://0.0.0.0:"+str(pyscardRpcPort))
363 |
--------------------------------------------------------------------------------
/sim/sim_backup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2016 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7 | from util import types
8 | from sim_soft import sim_xml
9 |
10 | class SimBackup(object):
11 | def __init__(self, simCtrl, imsi, atr):
12 | self.simCtrl = simCtrl
13 | self.imsi = imsi
14 | self.root = sim_xml.xmlInit(atr)
15 |
16 | def setMf(self, data):
17 | return self.addMf(self.root, data)
18 |
19 | def addMf(self, node, data):
20 | arrFile, arrRecord = types.getArrFileFromData(data)
21 | if not arrFile:
22 | #TODO: handle security attr
23 | arrFile = "2F06"
24 | arrRecord = "2"
25 | return sim_xml.addMfNode(node, str(arrFile), str(arrRecord))
26 |
27 | def addDf(self, node, id, data):
28 | arrFile, arrRecord = types.getArrFileFromData(data)
29 | if not arrFile:
30 | #TODO: handle security attr
31 | arrFile = "2F06"
32 | arrRecord = "2"
33 | return sim_xml.addDfNode(node, id, str(arrFile), str(arrRecord))
34 |
35 | def addAdf(self, node, id, aid, data):
36 | arrFile, arrRecord = types.getArrFileFromData(data)
37 | if not arrFile:
38 | #TODO: handle security attr
39 | arrFile = "2F06"
40 | arrRecord = "2"
41 | return sim_xml.addAdfNode(node, id, aid, str(arrFile), str(arrRecord))
42 |
43 | def addEf(self, node, id, value, data):
44 | struct = self.simCtrl.getFileStructure(data)
45 | if self.simCtrl.getFileStructure(data) in [types.FILE_STRUCTURE_LINEAR_FIXED,
46 | types.FILE_STRUCTURE_CYCLIC]:
47 | recordLength, nbrOfRecords = self.simCtrl.getRecordInfo(data)
48 | size = recordLength * nbrOfRecords
49 | else:
50 | recordLength = 0
51 | size = types.getFileLength(data)
52 | #TODO: for older 3G cards security attr might be used instead of arr
53 | arrFile, arrRecord = types.getArrFileFromData(data)
54 | if not arrFile:
55 | #TODO: handle security attr
56 | arrFile = "6F06"
57 | arrRecord = "2"
58 | sfi = "%02X" %types.getSfiFromData(data)
59 | #TODO: get from data
60 | invalidated = 0
61 | rwInvalidated = 0
62 | sim_xml.addEfNode(node,
63 | id,
64 | sfi,
65 | str(struct),
66 | str(size),
67 | str(recordLength),
68 | value,
69 | arrFile,
70 | str(arrRecord),
71 | str(invalidated),
72 | str(rwInvalidated))
73 |
74 | def saveXml(self):
75 | xmlPath = os.path.dirname(__file__) + "/../sim_soft/sim_backup_" + self.imsi + ".xml"
76 | sim_xml.writeXml(xmlPath, self.root)
77 | return xmlPath
--------------------------------------------------------------------------------
/sim/sim_card.py:
--------------------------------------------------------------------------------
1 | # LICENSE: GPL2
2 | # (c) 2013 Tom Schouten
3 | # (c) 2014, Kamil Wartanowicz
4 | import logging
5 | from util import hextools
6 | import sim_reader
7 | from util import types
8 | from util import types_g
9 |
10 | class SimCard(object):
11 | def __init__(self, index=0, mode=sim_reader.MODE_PYSCARD, type=types.TYPE_USIM):
12 | self.simReader = sim_reader.SimReader(mode=mode, type=type)
13 | self.index = None
14 | self.mode = mode
15 | self.imsi = None
16 | self.atr = None
17 | self.currentAidId = None # TODO: consider logical channels
18 | self.currentFile = CurrentFile() # TODO: consider logical channels
19 | self.routingAttr = RoutingAttr()
20 | self.swNoError = 0x9000
21 | self.type = type
22 | self.activeChannel = 0
23 | self.logicalChannelClosed = False
24 |
25 | def removeRoutingAttr(self):
26 | self.routingAttr.insCommon = []
27 | self.routingAttr.filesCommon = []
28 | self.routingAttr.filesReplaced = []
29 | self.routingAttr.insReplaced = []
30 |
31 | def listReaders(self):
32 | return self.simReader.listReaders()
33 |
34 | def removeAllReaders(self):
35 | return self.simReader.removeAllReaders()
36 |
37 | def connect(self, index=0):
38 | self.index = index
39 | readers = self.simReader.listReaders()
40 | self.simReader.addReader(index)
41 | self.simReader.r_createConnection(index)
42 | self.simReader.c_connect(index)
43 | self.clearLogicalChannels()
44 | self.getATR()
45 |
46 | def clearLogicalChannels(self):
47 | self.logicalChannelClosed = True
48 | self.activeChannel = 0
49 |
50 | def setActiveChannel(self, channel):
51 | self.activeChannel = channel
52 |
53 | def getActiveChannel(self):
54 | return self.activeChannel
55 |
56 | def stop(self):
57 | self.simReader.close()
58 |
59 | def disconnect(self):
60 | try:
61 | self.simReader.c_disconnect(self.index)
62 | except:
63 | logging.debug("Reader not connected")
64 | self.simReader.removeReader(self.index)
65 | self.atr = None
66 |
67 | def reset(self):
68 | #TODO: implement different solution, takes too much time on live SIM
69 | self.disconnect()
70 | self.connect(self.index)
71 |
72 | def getATR(self):
73 | self.atr = self.simReader.c_getATR(self.index)
74 | self.clearLogicalChannels()
75 | return self.atr
76 |
77 | def getCachedAtr(self):
78 | return self.atr
79 |
80 | def setImsi(self, imsi):
81 | self.imsi = imsi
82 |
83 | def getCurrentAidId(self):
84 | return self.currentAidId
85 |
86 | def setCurrentAidId(self, aidId):
87 | self.currentAidId = aidId
88 |
89 | # Perform APDU request on card
90 | def apdu(self, c_apdu):
91 | c_apdu = hextools.bytes(c_apdu)
92 | # Delegate
93 | try:
94 | (data,sw1,sw2) = self.simReader.c_transmit(list(c_apdu), self.index)
95 | except Exception as e:
96 | #TODO: Remove printing the error. Check why exception is not printed in tes_sat
97 | logging.error(str(e) + "\n\n")
98 | raise Exception("Failed to transmit C_APDU: " + hextools.bytes2hex(c_apdu) + "\n" + str(e))
99 | self.updateSwNoError(sw1, sw2)
100 | return pack(data,sw1,sw2)
101 |
102 | def updateSwNoError(self, sw1, sw2):
103 | "cache last success"
104 | sw = types.packSw(sw1, sw2)
105 | if sw == types_g.sw.NO_ERROR or sw1 == types_g.sw1.NO_ERROR_PROACTIVE_DATA:
106 | self.swNoError = sw
107 |
108 | def getCurrentFile(self):
109 | return self.currentFile
110 |
111 | def getCurrentFilePath(self):
112 | return self.currentFile.path
113 |
114 | def getCurrentFileType(self):
115 | return self.currentFile.type
116 |
117 | def getCurrentDirPath(self):
118 | if types.cmpBitwise(self.currentFile.type, types_g.fileDescriptor.DF_OR_ADF):
119 | path = self.currentFile.path
120 | else:
121 | path = types.parentDirFromPath(self.currentFile.path)
122 | return path
123 |
124 | def setCurrentFile(self, path, type):
125 | self.setCurrentFilePath(path)
126 | self.setCurrentFileType(type)
127 |
128 | def setCurrentFilePath(self, path):
129 | self.currentFile.path = path
130 |
131 | def setCurrentFileType(self, type):
132 | self.currentFile.type = type
133 |
134 | def appendCurrentDir(self, name, type=types_g.fileDescriptor.NO_INFORMATION_GIVEN):
135 | if name == "3F00":
136 | self.resetCurrentDir()
137 | return
138 | if name == "7FFF":
139 | aidId = self.getCurrentAidId()
140 | if aidId == None:
141 | aidId = 0
142 | self.resetCurrentDir()
143 | name = "ADF%d" %aidId
144 | type = types_g.fileDescriptor.DF_OR_ADF
145 | if not types.cmpBitwise(self.currentFile.type, types_g.fileDescriptor.DF_OR_ADF):
146 | path = types.parentDirFromPath(self.currentFile.path)
147 | self.setCurrentFile(path, types_g.fileDescriptor.DF_OR_ADF)
148 | if types.fidFromPath(self.currentFile.path) == name:
149 | #parrent directory name must be different than currently selected file
150 | return
151 | currentFilePath = self.currentFile.path
152 | if self.currentFile.path[-1] != "/":
153 | currentFilePath += "/"
154 | currentFilePath += name
155 | self.setCurrentFile(currentFilePath, type)
156 |
157 | def decrementCurrentDir(self):
158 | if not self.currentFile.path:
159 | return
160 | #path = types.parentDirFromPath(self.currentFile.path)
161 | path = types.parentDirFromPath(self.getCurrentDirPath())
162 | self.setCurrentFile(path, types_g.fileDescriptor.DF_OR_ADF)
163 |
164 | def resetCurrentDir(self):
165 | self.setCurrentFile("/", types_g.fileDescriptor.DF_OR_ADF)
166 |
167 | FILE_INS = [
168 | 'READ_BINARY',
169 | 'READ_RECORD',
170 | 'SEARCH_RECORD',
171 | 'UPDATE_BINARY',
172 | 'WRITE_BINARY',
173 | 'WRITE_RECORD',
174 | 'UPDATE_RECORD'
175 | ]
176 |
177 | SAT_INS = [
178 | 'TERMINAL_PROFILE',
179 | 'TERMINAL_RESPONSE',
180 | 'FETCH',
181 | 'ENVELOPE',
182 | ]
183 |
184 | FILES_AID = [
185 | 'EF_DIR',
186 | 'A000',
187 | ]
188 |
189 | FILES_REG = [
190 | 'EF_IMSI',
191 | 'EF_LOCI', #rplmn
192 | 'EF_SMSP', #sms center number
193 | 'EF_LRPLMNSI', #last RPLMN Selection Indication
194 | 'EF_PLMNSEL', #plmn selector
195 | 'EF_FPLMN', #forbiden plmn
196 |
197 | 'EF_PSLOCI',
198 | 'EF_EPSLOCI',
199 | 'EF_EPSNSC',
200 | 'EF_LOCIGPRS',
201 |
202 | 'EF_KEYS',
203 | 'EF_KEYSPS',
204 | 'EF_KCGPRS',
205 | 'EF_KC',
206 |
207 | 'EF_EHPLMN',
208 | 'EF_EHPLMNPI',
209 | 'EF_LRPLMNSI',
210 | ]
211 |
212 | '''
213 | #migth be also considered in FILES_REG
214 | 'EF_RPLMN_ACT',
215 | 'EF_PLMNWACT',
216 | 'EF_HIDDENKEY',
217 | 'EF_OPLMNWACT',
218 | 'EF_HPLMNWACT',
219 | 'RPLMN_ACT',
220 | '''
221 |
222 | FILES_REPLACED = []
223 |
224 | INS_REPLACED = []
225 |
226 | INS_COMMON = [
227 | 'GET_RESPONSE',
228 | 'SELECT_FILE',
229 | 'MANAGE_CHANNEL',
230 | #'INTERNAL_AUTHENTICATE',
231 | ]
232 |
233 | def pack(reply,sw1,sw2):
234 | p = list(reply)
235 | p.append(sw1)
236 | p.append(sw2)
237 | return p
238 |
239 | class RoutingAttr(object):
240 | def __init__(self):
241 | self.insCommon = list(INS_COMMON)
242 | self.filesCommon = list(FILES_AID)
243 | self.filesReplaced = list(FILES_REPLACED)
244 | self.insReplaced = list(INS_REPLACED)
245 |
246 | self.getResponse = None
247 | self.fileSelected = []
248 | self.aidToSelect = None
249 | self.recordEfDirLength = None
250 |
251 | def getFileSelected(self, channel):
252 | for file in self.fileSelected:
253 | if not channel or file[1] == channel:
254 | return file[0]
255 | return None
256 |
257 | def setFileSelected(self, file, channel):
258 | for i,fileDict in enumerate(self.fileSelected):
259 | if fileDict[1] == channel:
260 | self.fileSelected[i] = (file, channel)
261 | return
262 | self.fileSelected.append((file, channel))
263 |
264 | class CurrentFile(object):
265 | def __init__(self):
266 | self.path = "/"
267 | self.type = types_g.fileDescriptor.DF_OR_ADF
268 |
--------------------------------------------------------------------------------
/sim/sim_codes.py:
--------------------------------------------------------------------------------
1 | PIN_1 = 0
2 | PIN_1_UNBLOCK = 1
3 | PIN_2 = 3
4 | PIN_2_UNBLOCK = 4
5 | ADM_1 = 5
6 | ADM_2 = 6
7 | ADM_3 = 7
8 | ADM_4 = 8
9 | PIN_1_ENABLE = 10
10 | PIN_1_DISABLE = 11
11 | AUTH_KEY = 12
12 | AUTH_SQN = 13
13 | AUTH_AMF = 14
14 |
15 | defaultCard = {
16 | PIN_1 : "1111",
17 | PIN_1_UNBLOCK : "11111111",
18 | PIN_2 : "2222",
19 | PIN_2_UNBLOCK : "22222222",
20 | ADM_1 : "ADM11111",
21 | ADM_2 : "ADM22222",
22 | ADM_4 : "ADM44444",
23 | AUTH_KEY : "00112233445566778899AABBCCDDEEFF",
24 | AUTH_SQN : "000000000000",
25 | #AUTH_SQN : "FFFFFFFFFFFF",
26 | AUTH_AMF : "8000",
27 | }
--------------------------------------------------------------------------------
/sim/sim_files_2g.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | DF_GRAPHICS
7 |
8 | EF_IMG
9 |
10 |
11 | EF_ARR
12 |
13 |
14 |
15 |
16 | EF_ARR
17 |
18 |
19 |
20 | EF_CCP
21 |
22 |
23 | EF_ADN
24 |
25 |
26 | EF_SMS
27 |
28 |
29 | EF_FDN
30 |
31 | DF_TELECOM
32 |
33 | EF_BDN
34 |
35 |
36 | EF_EXT4
37 |
38 |
39 | EF_ECCP
40 |
41 |
42 | EF_EXT1
43 |
44 |
45 | EF_EXT2
46 |
47 |
48 | EF_EXT3
49 |
50 |
51 | EF_SDN
52 |
53 |
54 | EF_LND
55 |
56 |
57 | EF_SMSR
58 |
59 |
60 | EF_MSISDN
61 |
62 |
63 | EF_SMSP
64 |
65 |
66 | EF_SMSS
67 |
68 |
69 | EF_CMI
70 |
71 |
72 | EF_ARR
73 |
74 |
75 |
76 |
77 | EF_CPHS_ONSHF
78 |
79 |
80 | EF_CPHS_INFN
81 |
82 |
83 | DF_GLOBALSTAR
84 |
85 |
86 | EF_FPLMN
87 |
88 |
89 | EF_CPHS_VMW
90 |
91 |
92 | EF_CPHS_SST
93 |
94 |
95 | EF_CPHS_CFF
96 |
97 |
98 | EF_CPHS_ONSTR
99 |
100 |
101 | EF_CPHS_CSP
102 |
103 |
104 | EF_CPHS
105 |
106 |
107 | EF_CPHS_MBXN
108 |
109 |
110 | EF_ACC
111 |
112 |
113 | EF_BCCH
114 |
115 |
116 | DF_SOLSA
117 |
118 | EF_SAI
119 |
120 |
121 | EF_SLL
122 |
123 |
124 |
125 | DF_IRIDIUM
126 |
127 |
128 | EF_IMSI
129 |
130 |
131 | EF_ARR
132 |
133 |
134 | EF_LP
135 |
136 |
137 | EF_OPLMNWACT
138 |
139 |
140 | EF_PLMNWACT
141 |
142 |
143 | EF_CPBCCH
144 |
145 |
146 | EF_HPLMNWACT
147 |
148 |
149 | EF_RPLMN_ACT
150 |
151 |
152 | EF_INVSCAN
153 |
154 |
155 | EF_MMSUCP
156 |
157 |
158 | EF_MMSUP
159 |
160 |
161 | EF_MMSICP
162 |
163 |
164 | EF_EXT8
165 |
166 |
167 | EF_SPDI
168 |
169 |
170 | EF_MMSN
171 |
172 |
173 | EF_EXT7
174 |
175 |
176 | EF_MWIS
177 |
178 |
179 | DF_ACES
180 |
181 |
182 | EF_SUME
183 |
184 |
185 | DF_CTS
186 |
187 |
188 | EF_CBMIR
189 |
190 |
191 | EF_NIA
192 |
193 |
194 | EF_KCGPRS
195 |
196 |
197 | EF_LOCIGPRS
198 |
199 |
200 | EF_OPL
201 |
202 |
203 | EF_MBDN
204 |
205 |
206 | EF_PNN
207 |
208 |
209 | EF_EXT6
210 |
211 |
212 | EF_MBI
213 |
214 |
215 | DF_MEXE
216 |
217 | EF_MEXE-ST
218 |
219 |
220 | EF_ORPK
221 |
222 |
223 | EF_ARPK
224 |
225 |
226 | EF_TPRK
227 |
228 |
229 |
230 | DF_ICO
231 |
232 |
233 | EF_PUCT
234 |
235 |
236 | EF_SMSR
237 |
238 |
239 | EF_SPN
240 |
241 |
242 | EF_CBMI
243 |
244 |
245 | EF_CBMID
246 |
247 |
248 | EF_EMLPP
249 |
250 |
251 | EF_VBSS
252 |
253 |
254 | EF_ECC
255 |
256 |
257 | EF_AAEM
258 |
259 |
260 | EF_VGCS
261 |
262 |
263 | EF_VBS
264 |
265 |
266 | EF_VGCSS
267 |
268 |
269 | EF_GID2
270 |
271 |
272 | EF_GID1
273 |
274 |
275 | EF_AD
276 |
277 |
278 | EF_PHASE
279 |
280 |
281 | EF_ACMMAX
282 |
283 |
284 | EF_CNL
285 |
286 |
287 | EF_PLMNSEL
288 |
289 |
290 | EF_HPLMN
291 |
292 |
293 | EF_SST
294 |
295 |
296 | EF_ACM
297 |
298 | DF_GSM
299 |
300 | EF_LOCI
301 |
302 |
303 | EF_KC
304 |
305 |
306 | EF_CFIS
307 |
308 |
309 | EF_DCK
310 |
311 |
312 | DF_PCS_1900
313 |
314 |
315 | MF
316 |
317 | DF_IS_41
318 |
319 |
320 | DF_FP_CTS
321 |
322 |
323 | EF_ICCID
324 |
325 |
326 | EF_ELP
327 |
328 |
329 |
330 |
--------------------------------------------------------------------------------
/sim/sim_reader.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # Wrapper to pyscard
3 | # LICENSE: GPL2
4 | # (c) 2014 Kamil Wartanowicz
5 |
6 | from util import types
7 | import logging
8 | import threading
9 | import zerorpc
10 | from zerorpc.exceptions import *
11 | from sim_soft import sim_soft_ctrl
12 | from sim import pyscard_rpc_ctrl
13 |
14 | PYSCARD_RPC_IP_ADDRESS = "10.28.27.200"
15 | PYSCARD_RPC_PORT = 4148
16 |
17 | MODE_PYSCARD = 0
18 | MODE_SIM_SOFT = 1
19 |
20 | LOCAL_PYSCARD_SERVER = True
21 | MESSAGE_TIMEOUT = 60#sec
22 |
23 | class SimReader(object):
24 | def __init__(self, mode=MODE_PYSCARD, type=types.TYPE_USIM):
25 | self.mode = mode
26 | self.type = type
27 | self.server = None
28 | address=PYSCARD_RPC_IP_ADDRESS
29 | if mode == MODE_PYSCARD and LOCAL_PYSCARD_SERVER:
30 | logging.debug("Pyscard local server")
31 | self.server = pyscard_rpc_ctrl.PyscardRpcServerThread(PYSCARD_RPC_PORT)
32 | self.server.setDaemon(True)
33 | self.server.start()
34 | #set locall IP addr
35 | address = "127.0.0.1"
36 | self.address = address
37 | self.handlers = {}
38 |
39 | def close(self):
40 | threadId = threading.current_thread().ident
41 | if threadId in self.handlers.keys():
42 | try:
43 | self.handlers[threadId].close()
44 | except:
45 | pass
46 | del self.handlers[threadId]
47 | if self.server:
48 | self.server.close()
49 |
50 | def runClient(self, endpoint):
51 | # TODO: check that client might be connected otherwise kill server
52 | client = zerorpc.Client(heartbeat=None, timeout=MESSAGE_TIMEOUT)
53 | logging.debug("Pyscard RPC client: %r" %endpoint)
54 | client.connect(endpoint)
55 | return client
56 |
57 | def getHandler(self):
58 | if self.mode == MODE_SIM_SOFT:
59 | if len(self.handlers):
60 | # TODO: remove type from SimPyscard.
61 | # Return first element in dict.
62 | return self.handlers.itervalues().next()
63 | threadId = threading.current_thread().ident
64 | if threadId not in self.handlers.keys():
65 | if self.mode != MODE_SIM_SOFT:
66 | self.handlers.update({threadId : self.runClient("tcp://"+self.address+":"+str(PYSCARD_RPC_PORT))})
67 | else:
68 | self.handlers.update({threadId : sim_soft_ctrl.SimSoftCtrl(type=self.type)})
69 | return self.handlers[threadId]
70 |
71 | def __getattr__(self,attr):
72 | #__getattr__ is called when undefined method of class is called
73 | handler = self.getHandler()
74 | orig_attr = getattr(handler, attr)
75 |
76 | if not callable(orig_attr):
77 | #means the objct is: function, method or callable class
78 | #if orig_attr is not callable (not False) return attribute (e.g. class variable)
79 | return orig_attr
80 |
81 | def hooked(*args, **kwargs):
82 | result = 0
83 | try:
84 | result = orig_attr(*args, **kwargs)
85 | #if result == handler:
86 | # prevent wrapped_class from becoming unwrapped
87 | # return self
88 | except TimeoutExpired as exceptionObj:
89 | logging.warning("Timeout, repeat the last command")
90 | try:
91 | result = orig_attr(*args, **kwargs)
92 | except TimeoutExpired as exceptionObj:
93 | raise Exception(attr + "() failed!\n" + exceptionObj.message)
94 | except Exception as e:
95 | #catch every exception except TimeoutExpired(caught above)
96 | e = "".join(str(e)).rstrip("\n\n")
97 | e = e.split("\n")
98 | if len(e) >= 6:
99 | #limit stack
100 | e = e[-6:]
101 | raise Exception("%s() failed!\n%s" %(attr, "\n".join(e)))
102 | return result
103 | return hooked
104 |
105 | READER_ID_0 = 0
106 | READER_ID_1 = 1
107 | READER_ID_2 = 2
108 | READER_ID_3 = 3
--------------------------------------------------------------------------------
/sim_soft/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/sim_soft/__init__.py
--------------------------------------------------------------------------------
/sim_soft/sat_types.py:
--------------------------------------------------------------------------------
1 | # LICENSE: GPL2
2 | # (c) 2014 Szymon Mielczarek
3 |
4 | # Fixed lengths of some COMPREHENSION-TLV data objects
5 | TLV_COMMAND_DETAILS_LENGTH = 0x03
6 | TLV_DEVICE_IDENTITIES_LENGTH = 0x02
7 | TLV_DURATION_LENGTH = 0x02
8 | TLV_ITEM_IDENTIFIER_LENGTH = 0x01
9 | TLV_RESPONSE_LENGTH = 0x02
10 |
11 | class data_coding(object):
12 | #SMS class
13 | CODING_MESSAGE_CLASS_0 = 0b00000000
14 | CODING_MESSAGE_CLASS_1 = 0b00000001
15 | CODING_MESSAGE_CLASS_2 = 0b00000010
16 | CODING_MESSAGE_CLASS_3 = 0b00000011
17 |
18 | #character set
19 | CODING_GSM7 = 0b00000000
20 | CODING_8_BIT_DATA = 0b00000100
21 | CODING_UCS2 = 0b00001000
22 | CODING_RESERVED = 0b00001100
23 |
24 | class gsm_charset(object):
25 | CHARSET_UTF8 = 1
26 | CHARSET_UCS2 = 2
27 | CHARSET_GSM = 3
28 |
29 | class duration_time_unit(object):
30 | MINUTES = 0x00
31 | SECONDS = 0x01
32 | TENTHS_OF_SECONDS = 0x02
33 |
34 | # REFRESH modes (command qualifiers)
35 | class refresh_mode(object):
36 | # NAA Initialization and Full File Change Notification
37 | SIM_INIT_AND_FULL_FILE_CHANGE_NOTIFICATION = 0x00
38 | # File Change Notification
39 | FILE_CHANGE_NOTIFICATION = 0x01
40 | # NAA Initialization and File Change Notification
41 | SIM_INIT_AND_FILE_CHANGE_NOTIFICATION = 0x02
42 | # NAA Initialization
43 | SIM_INIT = 0x03
44 | # UICC Reset
45 | SIM_RESET = 0x04
46 | # NAA Application Reset, only applicable for a 3G platform
47 | USIM_INIT = 0x05
48 | # NAA Session Reset, only applicable for a 3G platform
49 | USIM_RESET = 0x06
50 |
51 | # LOCAL INFORMATION (command qualifiers)
52 | class local_info(object):
53 | # Location Information according to current NAA
54 | LOCATION_INFO_PER_NAA = 0x00
55 | # IMEI of the terminal
56 | IMEI_OF_THE_TERMINAL = 0x01
57 | # Network Measurement results according to current NAA
58 | NETWORK_MEASUREMENT_RESULTS_PER_NAA = 0x02
59 | # Date, time and time zone
60 | DATETIME = 0x03
61 | # Language setting
62 | LANGUAGE = 0x04
63 | # Reserved for GSM
64 | RESERVED_FOR_GSM = 0x05
65 | # Access Technology (single access technology)
66 | ACCESS_TECHNOLOGY = 0x06
67 | # ESN of the terminal
68 | ESN_OF_THE_TERMINAL = 0x07
69 | # IMEISV of the terminal
70 | IMEISV_OF_THE_TERMINAL = 0x08
71 | # Search Mode
72 | SEARCH_MODE = 0x09
73 | # Charge State of the Battery (if class "g" is supported)
74 | BATTERY_CHARGE_STATE = 0x0A
75 | # MEID of the terminal
76 | MEID_OF_THE_TERMINAL = 0x0B
77 | # reserved for 3GPP (current WSID)
78 | RESERVED_FOR_3GPP = 0x0C
79 | # Broadcast Network information
80 | # according to current Broadcast Network Technology used
81 | BROADCAST_NETWORK_INFO = 0x0D
82 | # Multiple Access Technologies
83 | MULTIPLE_ACCESS_TECHNOLOGY = 0x0E
84 | # Location Information for multiple access technologies
85 | LOCATION_INFO_FOR_MULTITECH = 0x0F
86 | # Network Measurement results for multiple access technologies
87 | NETWORK_MEASUREMENT_RESULTS_FOR_MULTITECH = 0x10
88 | # '11' to 'FF' = Reserved.
89 |
90 | # Tag values used to identify the BER-TLV and COMPREHENSION-TLV data objects
91 | # TS 102.223 v12.1.0 (sections 9.1-9.3)
92 | # TS 101 220 V8.4.0 (page 14)
93 | # BER (Basing encoding rules)
94 | class ber_tag(object):
95 | PROACTIVE = 0xD0
96 | SMS_PP_DOWNLOAD = 0xD1
97 | CELL_BROADCAST_DOWNLOAD = 0xD2
98 | MENU_SELECTION = 0xD3
99 | CALL_CONTROL = 0xD4
100 | MO_SHORT_MSG_CONTROL = 0xD5 # GSM/3G
101 | EVENT_DOWNLOAD = 0xD6
102 | TIMER_EXPIRATION = 0xD7
103 | USSD_DOWNLOAD = 0xD9 # 3G
104 | MMS_TRANSFER_STATUS = 0xDA
105 | MMS_NOTIFICATION_DOWNLOAD = 0xDB
106 | TERMINAL_APPLICATION = 0xDC
107 | # and some more
108 |
109 | class comprehension_tag(object):
110 | COMPREHENSION_REQUIRED = 0x80
111 | COMMAND_DETAILS = 0x01
112 | DEVICE_IDENTITIES = 0x02
113 | RESULT = 0x03
114 | DURATION = 0x04
115 | ALPHA_IDENTIFIER = 0x05
116 | ADDRESS = 0x06
117 | CAPABILITY_CONFIGURATION_PARAMETERS = 0x07
118 | CALLED_PARTY_SUBADDRESS = 0x08
119 | SS_STRING = 0x09 # Reserved for GSM/3G
120 | USSD_STRING = 0x0A # Reserved for GSM/3G
121 | SMS_TPDU = 0x0B # Reserved for GSM/3G
122 | CELL_BROADCAST_PAGE = 0x0C # Reserved for GSM/3G
123 | TEXT_STRING = 0x0D
124 | TONE = 0x0E
125 | ECAT_CLIENT_PROFILE = 0x0E
126 | ITEM = 0x0F
127 | ECAT_CLIENT_IDENTITY = 0x0F
128 | ITEM_IDENTIFIER = 0x10
129 | ENCAPSULATED_ENVELOPE = 0x10
130 | RESPONSE_LENGTH = 0x11
131 | FILE_LIST = 0x12
132 | LOCATION_INFORMATION = 0x13
133 | IMEI = 0x14
134 | HELP_REQUEST = 0x15
135 | ICON_IDENTIFIER = 0x1E
136 | # and many more (up to 0x7B)
137 |
138 | # TS 102.223 v12.1.0 (section 9.4)
139 | class cmd_type(object):
140 | REFRESH = 0x01
141 | MORE_TIME = 0x02
142 | POLL_INTERVAL = 0x03
143 | POLLING_OFF = 0x04
144 | SETUP_EVENT_LIST = 0x05
145 | SET_UP_CALL = 0x10
146 | SEND_SS = 0x11
147 | SEND_USSD = 0x12
148 | SEND_SHORT_MESSAGE = 0x13
149 | SEND_DTMF = 0x14
150 | LAUNCH_BROWSER = 0x15
151 | GEOGRAPHICAL_LOCATION_REQUEST = 0x16
152 | PLAY_TONE = 0x20
153 | DISPLAY_TEXT = 0x21
154 | GET_INKEY = 0x22
155 | GET_INPUT = 0x23
156 | SELECT_ITEM = 0x24
157 | SET_UP_MENU = 0x25
158 | PROVIDE_LOCAL_INFO = 0x26
159 | TIMER_MANAGEMENT = 0x27
160 | SETUP_IDLE_MODE_TEXT = 0x28
161 | CARD_APDU = 0x30
162 | POWER_ON_CARD = 0x31
163 | POWER_OFF_CARD = 0x32
164 | GET_READER_STATUS = 0x33
165 | RUN_AT_COMMAND = 0x34
166 | LANG_NOTIFICATION = 0x35
167 | OPEN_CHANNEL = 0x40
168 | CLOSE_CHANNEL = 0x41
169 | RECEIVE_DATA = 0x42
170 | SEND_DATA = 0x43
171 | GET_CHANNEL_STATUS = 0x44
172 | SERVICE_SEARCH = 0x45
173 | GET_SERVICE_INFORMATION = 0x46
174 | DECLARE_SERVICE = 0x47
175 | SET_FRAMES = 0x50
176 | GET_FRAMES_STATUS = 0x51
177 | RETRIEVE_MM = 0x60
178 | SUBMIT_MM = 0x61
179 | DISPLAY_MM = 0x62
180 | ACTIVATE = 0x70
181 | CONTACTLESS_STATE_CHANGED = 0x71
182 | COMMAND_CONTAINER = 0x72
183 | ENCAPSULATED_SESSION_CONTROL = 0x73
184 | END_OF_PROACTIVE_SESSION = 0x81
185 |
186 | # TS 102.223 v12.1.0 (section 8.7)
187 | class device_identity(object):
188 | DEV_KEYPAD = 0x01
189 | DEV_DISPLAY = 0x02
190 | DEV_EARPIECE = 0x03
191 | DEV_ADDT_CARD_READER = 0x10
192 | # card reader id (0 to 7)
193 | DEV_CHANNEL_IDENTIFIER = 0x20
194 | # channel id (1 to 7)
195 | DEV_ECAT_CLIENT_IDENTIFIER = 0x30
196 | # client id (1 to 15)
197 | DEV_UICC = 0x81
198 | DEV_TERMINAL = 0x82
199 | DEV_NETWORK = 0x83
200 |
201 | # 8.12 Result: General result
202 | general_result = {
203 | 0x00 : 'Command performed successfully',
204 | 0x01 : 'Command performed with partial comprehension',
205 | 0x02 : 'Command performed, with missing information',
206 | 0x03 : 'REFRESH performed with additional Efs read',
207 | 0x04 : 'Command performed successfully, but requested icon could not be displayed',
208 | 0x05 : 'Command performed, but modified by call control by NAA',
209 | 0x06 : 'Command performed successfully, limited service',
210 | 0x07 : 'Command performed with modification; ETSI',
211 | # Release 12 151 ETSI TS 102 223 V12.1.0 (2014-09)
212 | 0x08 : 'REFRESH performed but indicated NAA was not active',
213 | 0x09 : 'Command performed successfully, tone not played',
214 | 0x10 : 'Proactive UICC session terminated by the user',
215 | 0x11 : 'Backward move in the proactive UICC session requested by the user',
216 | 0x12 : 'No response from user',
217 | 0x13 : 'Help information required by the user',
218 | 0x14 : 'USSD or SS transaction terminated by the user',
219 | # Results '0X' and '1X' indicate that the command has been performed:
220 | 0x20 : 'terminal currently unable to process command',
221 | 0x21 : 'Network currently unable to process command',
222 | 0x22 : 'User did not accept the proactive command',
223 | 0x23 : 'User cleared down call before connection or network release',
224 | 0x24 : 'Action in contradiction with the current timer state',
225 | 0x25 : 'Interaction with call control by NAA, temporary problem',
226 | 0x26 : 'Launch browser generic error code',
227 | 0x27 : 'MMS temporary problem',
228 | # Results '2X' indicate to the UICC that
229 | # it may be worth re-trying the command at a later opportunity:
230 | 0x30 : 'Command beyond terminal\'s capabilities',
231 | 0x31 : 'Command type not understood by terminal',
232 | 0x32 : 'Command data not understood by terminal',
233 | 0x33 : 'Command number not known by terminal',
234 | 0x34 : 'SS Return Error',
235 | 0x35 : 'SMS RP-ERROR',
236 | 0x36 : 'Error, required values are missing',
237 | 0x37 : 'USSD Return Error',
238 | 0x38 : 'MultipleCard commands error',
239 | 0x39 : 'Interaction with call control by NAA, permanent problem',
240 | 0x3A : 'Bearer Independent Protocol error',
241 | 0x3B : 'Access Technology unable to process command',
242 | 0x3C : 'Frames error',
243 | 0x3D : 'MMS Error',
244 | # Results '3X' indicate that it is not worth
245 | # the UICC re-trying with an identical command, as it will only get the
246 | #same response. However, the decision to retry lies with the application.
247 | }
248 |
--------------------------------------------------------------------------------
/sim_soft/sim_auth.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2015 Mikolaj Bartnicki
4 | # (c) 2015 Kamil Wartanowicz
5 | import logging
6 | """
7 | USIM authentication algorithm.
8 | Names of Dummy XOR variables
9 |
10 | Variable | bits | name
11 | ======================================
12 | key | 128 | Key Value (K)
13 | rand 128 Random Value (RAND)
14 | amf | 16 | AMF
15 | sqn | 48 | SQN
16 |
17 | ak | 48 | AK
18 | mac | 64 | MAC
19 | autn | 128 | AUTN
20 |
21 | res | 32-128| RES (XDOUT RES)
22 | ck | 128 | CK
23 | ik | 128 | IK
24 | kc | 128 | KC
25 | """
26 |
27 | # masks used to cut lengths of values
28 | MASK_48_BIT = 0xFFFFFFFFFFFF
29 | MASK_64_BIT = 0xFFFFFFFFFFFFFFFF
30 | MASK_128_BIT = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
31 |
32 | AUTH_RESULT_OK = 0xDB
33 | AUTH_RESULT_SYNCHORNISATION_FAILURE = 0xDC
34 |
35 | def rotateLeft128bit(i, n):
36 | """Rotate left i by n bytes.
37 | The result value lenght is set to 128 bits."""
38 | mask = (i >> (128 - n)) & MASK_128_BIT
39 | i = ((i << n) | mask) & MASK_128_BIT
40 | return i
41 |
42 | def dummyXor(key, rand, sqn, amf, autn):
43 | """Perform a Dummy XOR authentication algorithm."""
44 | #http://cosconor.fr/GSM/Divers/Others/Cours/SIM%20Cards/GemXplore3G%20V2.pdf
45 | xdout = key ^ rand
46 | ck = rotateLeft128bit(xdout, 8)
47 | ik = rotateLeft128bit(xdout, 16)
48 | ak = (xdout >> 56) & MASK_48_BIT
49 |
50 | if autn:
51 | sqnFromAutn = (autn >> 80) ^ ak
52 | if sqnFromAutn != sqn:
53 | #TODO: implement sqn range check and EF_SQN update
54 | logging.info("Received sqn: %s, expected: %s" %(keyHex(sqnFromAutn), keyHex(sqn)))
55 | sqn = sqnFromAutn
56 | cdout = (sqn << 16) | amf
57 | xmac = (xdout >> 64) ^ cdout
58 | outAutn = ((sqn ^ ak) << 80) | (amf << 64) | xmac
59 | kc = (ik & MASK_64_BIT) ^ (ik >> 64) ^ (ck & MASK_64_BIT) ^ (ck >> 64)
60 | return {'res': xdout, 'autn' : outAutn, 'ck': ck, 'ik': ik, 'kc' : kc}
61 |
62 | def dummyXorHex(key, rand, sqn, amf, autn):
63 | key = int(key, 16)
64 | rand = int(rand, 16)
65 | sqn = int(sqn, 16)
66 | amf = int(amf, 16)
67 | if autn:
68 | autn = int(autn, 16)
69 | result = dummyXor(key, rand, sqn, amf, autn)
70 | for key in result:
71 | if key != 'kc':
72 | result[key] = keyHex(result[key])
73 | else:
74 | result[key] = keyHex(result[key], length=8)
75 | return result
76 |
77 | def keyHex(key, length=16):
78 | length = 2 * length
79 | data = "%02X" %(key)
80 | currentlength = len(data)
81 | if currentlength < length:
82 | data = "%s%s" %("0" * (length-currentlength), data)
83 | return data
84 |
85 | def dummyXorData(rand, key, sqn, amf, autn=None):
86 | result = dummyXorHex(key, rand, sqn, amf, autn)
87 | if result['autn'] != autn:
88 | return []
89 | data = "%02X" %AUTH_RESULT_OK
90 | data += "%02X%s" %(len(result['res'])/2, result['res'])
91 | data += "%02X%s" %(len(result['ck'])/2, result['ck'])
92 | data += "%02X%s" %(len(result['ik'])/2, result['ik'])
93 | #service 27 available in EF_UST
94 | data += "%02X%s" %(len(result['kc'])/2, result['kc'])
95 | return data
96 |
97 | def authenticateDummyXor(rand, key, sqn, amf, autn=None):
98 | return dummyXorData(rand, key, sqn, amf, autn)
--------------------------------------------------------------------------------
/sim_soft/sim_soft_ctrl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import inspect
6 | import logging
7 | import os
8 | import sys
9 | import traceback
10 |
11 | import sim_xml
12 | import sim_soft
13 | import sat_ctrl
14 |
15 | from util import types_g
16 | from util import types
17 |
18 | class SoftCard(object):
19 | def __init__(self, simType=types.TYPE_USIM, file=None):
20 | self.simType = simType
21 | if not file:
22 | file = os.path.dirname(__file__) + "/sim_backup.xml"
23 | self.file = file
24 |
25 | def connect(self):
26 | self.init()
27 |
28 | def init(self):
29 | #TODO: check if already opened, then close might be needed
30 | self.simXml = sim_xml.SimXml(self.file)
31 | self.satCtrl = sat_ctrl.SatCtrl(types.TYPE_SIM, self.simXml)
32 | self.simHandler = sim_soft.SimHandler(self.simXml, self.satCtrl, self.simType)
33 |
34 | def getATR(self):
35 | self.init()
36 | return self.simXml.getAtr()
37 |
38 | def transmit(self, apdu):
39 | try:
40 | return self._transmit(apdu)
41 | except:
42 | exc_type, exc_value, exc_traceback = sys.exc_info()
43 | stackTrace = "".join(traceback.format_tb(exc_traceback))
44 | error = exc_value
45 | raise Exception("%s\nError: %s" %(stackTrace, error))
46 |
47 | def _transmit(self, apdu):
48 | sw = 0x9000
49 | data = []
50 |
51 | channel = types.channel(apdu)
52 | if channel > types.MAX_LOGICAL_CHANNELS - 1:
53 | sw = types_g.sw.CLASS_NOT_SUPPORTED
54 | return data, sw
55 |
56 | # Check if the channel is open
57 | if self.simHandler.isChannelOpen(channel) == False:
58 | sw = types_g.sw.LOGICAL_CHANNEL_NOT_SUPPORTED
59 | return data, sw
60 | # TODO: open channel through SELECT command
61 | self.simHandler.setChannel(channel)
62 |
63 | ins = types.insName(apdu)
64 | if ins == "SELECT_FILE":
65 | data, sw = self.simHandler.select(apdu)
66 | elif ins == "GET_RESPONSE":
67 | data, sw = self.simHandler.getResponse(apdu)
68 | elif ins == "READ_BINARY":
69 | data, sw = self.simHandler.readBinary(apdu)
70 | elif ins == "VERIFY_PIN":
71 | data, sw = self.simHandler.verifyPin(apdu)
72 | elif ins == "UNBLOCK_PIN":
73 | data, sw = self.simHandler.unblockPin(apdu)
74 | elif ins == "CHANGE_PIN":
75 | data, sw = self.simHandler.changePin(apdu)
76 | elif ins == "ENABLE_PIN":
77 | data, sw = self.simHandler.enablePin(apdu)
78 | elif ins == "DISABLE_PIN":
79 | data, sw = self.simHandler.disablePin(apdu)
80 | elif ins == "STATUS":
81 | data, sw = self.simHandler.status(apdu)
82 | elif ins == "SEARCH_RECORD":
83 | data, sw = self.simHandler.searchRecord(apdu)
84 | elif ins == "READ_RECORD":
85 | data, sw = self.simHandler.readRecord(apdu)
86 | elif ins == "UPDATE_BINARY":
87 | data, sw = self.simHandler.updateBinary(apdu)
88 | elif ins == "UPDATE_RECORD":
89 | data, sw = self.simHandler.updateRecord(apdu)
90 | elif ins == "DEACTIVATE_FILE":
91 | data, sw = self.simHandler.deactivateFile(apdu)
92 | elif ins == "ACTIVATE_FILE":
93 | data, sw = self.simHandler.activateFile(apdu)
94 | elif ins == "TERMINAL_PROFILE":
95 | data, sw = self.satCtrl.terminalProfile(apdu)
96 | elif ins == "TERMINAL_RESPONSE":
97 | data, sw = self.satCtrl.terminalResponse(apdu)
98 | elif ins == "FETCH":
99 | data, sw = self.satCtrl.fetch(apdu)
100 | elif ins == "ENVELOPE":
101 | data, sw = self.satCtrl.envelope(apdu)
102 | elif ins == "MANAGE_CHANNEL":
103 | data, sw = self.simHandler.manageChannel(apdu)
104 | elif ins == 'INTERNAL_AUTHENTICATE':
105 | data, sw = self.simHandler.authenticate(apdu)
106 | elif ins == "CREATE_FILE":
107 | data, sw = self.simHandler.createFile(apdu)
108 | elif ins == "DELETE_FILE":
109 | data, sw = self.simHandler.deleteFile(apdu)
110 | elif ins == "RESIZE_FILE":
111 | data, sw = self.simHandler.resizeFile(apdu)
112 | elif ins == 'DE':
113 | data, sw = self.simHandler.unknownInstruction()
114 | else:
115 | raise Exception("Invalid instruction: %s" %ins)
116 |
117 | return data, sw
118 |
119 | class SoftReader(object):
120 | def __init__(self, name):
121 | self.name = name
122 | self.card = None
123 |
124 | def createConnection(self, type=types.TYPE_SIM):
125 | self.card = SoftCard(simType=type)
126 | return self.card
127 |
128 | reader1 = SoftReader('Soft SIM reader 0')
129 | reader2 = SoftReader('Soft SIM reader 1')
130 |
131 | READERS = [reader1, reader2]
132 |
133 | class Reader(object):
134 | def __init__(self):
135 | self.index = None
136 | self.reader = None
137 | self.card = None
138 |
139 | class SimSoftCtrl(object):
140 | def __init__(self, type=types.TYPE_USIM, logLevel=logging.INFO):
141 | dir = os.path.dirname(__file__)
142 | resultFile = dir + "/../sim_soft.log"
143 | FORMATTER = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S')
144 | fileHndl = logging.FileHandler(resultFile, mode='w')
145 | fileHndl.setFormatter(FORMATTER)
146 | fileHndl.setLevel(logLevel)
147 | logger = logging.getLogger("sim_soft")
148 | #dont't propagate to root logger
149 | logger.propagate=False
150 | logger.handlers = []
151 | logger.setLevel(logLevel)
152 | logger.addHandler(fileHndl)
153 | self.logging = logger
154 | self.readers = []
155 | self.simType = type
156 |
157 | def close(self):
158 | pass
159 |
160 | def getReader(self, index):
161 | for reader in self.readers:
162 | if reader.index == index:
163 | return reader
164 | return None
165 |
166 | def getReaderName(self, index):
167 | reader = self.getReader(index)
168 | if not reader:
169 | return None
170 | return reader.reader.name
171 |
172 | def getCard(self, index):
173 | self.checkReader(index)
174 | return self.getReader(index).card
175 |
176 | def checkReader(self, index):
177 | if not self.getReader(index):
178 | raise Exception("Reader with index=%d not created" %index)
179 |
180 | def checkCard(self, index):
181 | if not self.getCard(index):
182 | raise Exception("Card for reader with index=%d not created" %index)
183 |
184 | #exported methods
185 | def listReaders(self):
186 | self.logFunctionAndArgs()
187 | readers = READERS
188 | readersStr = []
189 | for reader in readers:
190 | readersStr.append(reader.name)
191 | self.logReturnVal(readersStr=readersStr)
192 | return readersStr
193 |
194 | def addReader(self, index):
195 | self.logFunctionAndArgs()
196 | self.readers.append(Reader())
197 | readersConnected = READERS
198 | if not len(readersConnected):
199 | raise Exception("No reader connected")
200 | if index >= len(readersConnected):
201 | raise Exception("Reader id:%d not connected, number of connected readers:%d"
202 | %(index, len(readersConnected)))
203 | self.readers[-1].index = index
204 | self.readers[-1].reader = readersConnected[index]
205 | self.logReturnVal()
206 | return None
207 |
208 | def removeReader(self, index):
209 | self.logFunctionAndArgs()
210 | self.checkReader(index)
211 | for reader in self.readers:
212 | if reader.index == index:
213 | del reader
214 | self.logReturnVal()
215 | return None
216 |
217 | def removeAllReaders(self):
218 | self.logFunctionAndArgs()
219 | for reader in self.readers:
220 | del reader
221 | self.logReturnVal()
222 | return None
223 |
224 | def r_createConnection(self, index):
225 | self.logFunctionAndArgs()
226 | self.checkReader(index)
227 | self.getReader(index).card = self.getReader(index).reader.createConnection(type=self.simType)
228 | return None
229 |
230 | def c_connect(self, index):
231 | self.logFunctionAndArgs()
232 | self.checkCard(index)
233 | self.getCard(index).connect()
234 | self.logReturnVal()
235 | return None
236 |
237 | def c_disconnect(self, index):
238 | self.logFunctionAndArgs()
239 | self.checkCard(index)
240 | self.getCard(index).disconnect()
241 | self.logReturnVal()
242 | return None
243 |
244 | def c_getATR(self, index):
245 | self.logFunctionAndArgs()
246 | self.checkCard(index)
247 | atr = self.getCard(index).getATR()
248 | self.logReturnVal(atr=atr)
249 | return atr
250 |
251 | def c_transmit(self, apdu, index):
252 | self.logFunctionAndArgs()
253 | self.checkCard(index)
254 | data, sw = self.getCard(index).transmit(apdu)
255 | sw1 = sw>>8
256 | sw2 = sw & 0x00FF
257 | self.logReturnVal(data=data, sw1=sw1, sw2=sw2)
258 | return data, sw1, sw2
259 |
260 | def logFunctionAndArgs(self):
261 | frame = inspect.getouterframes(inspect.currentframe())[1][0]
262 | args, _, _, values = inspect.getargvalues(frame)
263 | frameinfo = inspect.getframeinfo(frame)
264 | functionName=inspect.getframeinfo(frame)[2]
265 | output = ""
266 | for arg in args[1:]: #[1:] skip the first argument 'self'
267 | value = values[arg]
268 | if isinstance(value, str):
269 | #add apostrophes for string values
270 | value = "\'"+value+"\'"
271 | elif isinstance(value, int):
272 | value = ''.join('%02X' % value)
273 | else:
274 | newValue = ""
275 | for i in value:
276 | if isinstance(i, int):
277 | newValue += '%02X' % i
278 | else:
279 | newValue += str(i)
280 | value = newValue
281 | output += arg + '=' + value
282 | if arg != args[-1]:
283 | #add comma if not the last element
284 | output +=','
285 | #do not print "\n' as a new line
286 | output = output.replace("\n","\\n")
287 | self.logging.info("--> "+functionName+'('+output+')')
288 |
289 | def logReturnVal(self, **kwargs):
290 | output = ""
291 | for key, value in kwargs.iteritems():
292 | if isinstance(value, str):
293 | #add apostrophes for string values
294 | value = "\'"+value+"\'"
295 | elif isinstance(value, int):
296 | value = ''.join('%02X' % value)
297 | else:
298 | newValue = ""
299 | for i in value:
300 | if isinstance(i, int):
301 | newValue += '%02X' % i
302 | else:
303 | newValue += str(i)
304 | value = newValue
305 | output += key + ':' + value + ', '
306 | output = output.rstrip(', ') #remove last comma and space
307 | self.logging.info("<-- "+output+'\n')
308 |
--------------------------------------------------------------------------------
/simlab.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2015 Kamil Wartanowicz
4 | # (c) 2015 Szymon Mielczarek
5 |
6 | import os, sys
7 | import Tkinter as tk
8 | import subprocess as sub
9 |
10 | # Note: python-tk package needs to be installed on Linux machine
11 |
12 | RESOURCE_DIR = 'res'
13 | MIM_DIR = 'mim'
14 | FGCOLOR = '#FFFFFF'
15 | BGCOLOR = '#1F67B1'
16 | HIGHLIGHT_BGCOLOR = '#3E7BBC'
17 | TITLE_FONT = "Verdana 16"
18 | LABEL1_FONT = "Verdana 10 bold"
19 | LABEL2_FONT = "Verdana 7"
20 |
21 | MIM_OPTIONS = [
22 | {'script': 'mim_live.py',
23 | 'text1': 'Live', 'text2': 'USIM Live', 'icon': "live.gif"},
24 | {'script': 'mim_soft.py',
25 | 'text1': 'Soft', 'text2': 'USIM Soft with SAT', 'icon': "soft.gif"},
26 | {'script': 'mim_live_live.py',
27 | 'text1': 'Live, Live', 'text2': 'USIM Live, USIM Live with AUTH', 'icon': "live_live.gif"},
28 | {'script': 'mim_live_soft_sat.py',
29 | 'text1': 'Live, Soft SAT', 'text2': 'USIM Live, SAT Soft', 'icon': "live_soft.gif"},
30 | {'script': 'mim_soft_live_reg.py',
31 | 'text1': 'Soft, Live', 'text2': 'USIM Soft with SAT, USIM Live with AUTH and REG', 'icon': "soft_live.gif"},
32 | {'script': 'mim_soft_live_auth.py',
33 | 'text1': 'Soft, Live', 'text2': 'USIM Soft with SAT, USIM Live with AUTH ins', 'icon': "soft_live.gif"},
34 | {'script': 'mim_live_live_soft_sat.py',
35 | 'text1': 'Live, Live, Soft SAT','text2': 'USIM Live, USIM Live, SAT Soft', 'icon': "live_live_soft.gif"}
36 | ]
37 |
38 | def runScript(event, root, scriptName):
39 | print scriptName
40 | root.destroy() # close Tk window and mainloop
41 | absPath = os.path.abspath(scriptName)
42 | sub.call(['python', absPath], shell=False)
43 |
44 | def retag(tag, *args):
45 | '''Add the given tag as the first bindtag for every widget passed in'''
46 | for widget in args:
47 | widget.bindtags((tag,) + widget.bindtags())
48 |
49 | def on_enter(event, widgets, color):
50 | event.widget.configure(bg=color)
51 | for w in widgets:
52 | w.configure(bg=color)
53 |
54 | def on_leave(event, widgets, color):
55 | event.widget.configure(bg=color)
56 | for w in widgets:
57 | w.configure(bg=color)
58 |
59 | def main():
60 | root = tk.Tk()
61 |
62 | lbl = tk.Label(root, text="Choose an option", fg=FGCOLOR, bg=BGCOLOR, font=TITLE_FONT)
63 | lbl.pack(pady=(10,0))
64 |
65 | # Main frame
66 | fraMain = tk.Frame(root)
67 | fraMain.pack(padx=10, pady=10)
68 |
69 | optionsNum = len(MIM_OPTIONS)
70 | imgIcons = [None] * optionsNum
71 | lblImages = [None] * optionsNum
72 | lblText1 = [None] * optionsNum
73 | lblText2 = [None] * optionsNum
74 | fraFields = [None] * optionsNum
75 | i = 0
76 | for r in MIM_OPTIONS:
77 | fraFields[i] = tk.Frame(fraMain, bg=BGCOLOR)
78 | # Images
79 | imgIcons[i] = tk.PhotoImage(file=(os.path.join(RESOURCE_DIR, r['icon'])))
80 | lblImages[i] = tk.Label(fraFields[i], image=imgIcons[i], bg=BGCOLOR)
81 | lblImages[i].pack(side=tk.LEFT, padx=5, pady=5)
82 | # Labels
83 | lblText1[i] = tk.Label(fraFields[i], text=r['text1'], fg=FGCOLOR, bg=BGCOLOR, font=LABEL1_FONT)
84 | lblText1[i].pack(pady=(10,0), anchor=tk.W) #side=tk.LEFT,
85 | lblText2[i] = tk.Label(fraFields[i], text=r['text2'], fg=FGCOLOR, bg=BGCOLOR, font=LABEL2_FONT)
86 | lblText2[i].pack(anchor=tk.W, padx=(0,3)) #side=tk.BOTTOM,
87 | # Frames
88 | fraFields[i].bind("", lambda event, i=i: on_enter(event, [lblImages[i], lblText1[i], lblText2[i]], HIGHLIGHT_BGCOLOR))
89 | fraFields[i].bind("", lambda event, i=i: on_leave(event, [lblImages[i], lblText1[i], lblText2[i]], BGCOLOR))
90 | fraFields[i].pack(fill=tk.BOTH)
91 |
92 | name = r['script']
93 | tag = "special" + str(i)
94 | retag(tag, fraFields[i], lblImages[i], lblText1[i], lblText2[i])
95 | root.bind_class(tag, "", lambda event, name=name: runScript(event, root, os.path.join(MIM_DIR, name)))
96 | i = i + 1
97 |
98 | root.configure(bg=BGCOLOR)
99 | root.resizable(width=tk.FALSE, height=tk.FALSE)
100 | if os.name != 'posix':
101 | #workaround, do not load icon on unix
102 | try:
103 | root.iconbitmap(os.path.join(RESOURCE_DIR, 'live.ico'))
104 | except tk.TclError:
105 | print 'No ico file found'
106 | root.title("simLAB")
107 | root.mainloop()
108 |
109 | if __name__ == '__main__':
110 | main()
111 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/tests/__init__.py
--------------------------------------------------------------------------------
/tests/live/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/tests/live/__init__.py
--------------------------------------------------------------------------------
/tests/live/test_debug.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
7 |
8 | from sim import sim_router
9 | from sim import sim_card
10 | from util import hextools
11 | from util import types_g
12 | from util import types
13 | import unittest
14 | import logging
15 | import time
16 |
17 | from sim import sim_reader
18 | from sim import sim_ctrl_2g
19 | from sim import sim_ctrl_3g
20 | from sim import sim_shell
21 |
22 | MODE_SIM = sim_reader.MODE_PYSCARD
23 | SIM_TYPE = types.TYPE_USIM
24 |
25 | PIN_1 = "1111"
26 | PUK_1 = "11111111"
27 |
28 | PIN_1_FALSE = "1112"
29 | PUK_1_FALSE = "11111112"
30 |
31 | class TestDebug(unittest.TestCase):
32 | @classmethod
33 | def setUpClass(cls):
34 | cls.sim1 = sim_card.SimCard()
35 | cls.sim1.removeAllReaders()
36 | cls.sim1.connect(0)
37 | cls.sim2 = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=SIM_TYPE)
38 | try:
39 | cls.sim2.connect(0)
40 | except Exception as e:
41 | if "no card in reader" in str(e):
42 | cls.sim2.stop()
43 | raise Exception("No card in reader")
44 | cls.simRouter = sim_router.SimRouter(cards=[cls.sim1, cls.sim2], type=types.TYPE_USIM, mode=sim_router.SIMTRACE_OFFLINE)
45 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED)
46 | #update soft SIM for SAT handling
47 | cls.simRouter.getMainCard(1).routingAttr.insReplaced = sim_card.SAT_INS
48 | cls.sendApdu = cls.simRouter.simCtrl.sendApdu
49 | cls.shell = cls.simRouter.shell
50 |
51 | def test_1_send_apdu(self):
52 | #self.simCtrl.selectAid()
53 | sw1, sw2, data = self.sendApdu("801000001EFFFFFFFF7F9F00DFFF00001FE2000000C3"
54 | "FB000700016800710000000018",
55 | mode=sim_router.INJECT_WITH_FORWARD)
56 | sw1, sw2, data = self.sendApdu("8012000076", mode=sim_router.INJECT_WITH_FORWARD)
57 | sw1, sw2, data = self.sendApdu("801400000C810301250002028281830100",
58 | mode=sim_router.INJECT_WITH_FORWARD)
59 | sw1, sw2, data = self.sendApdu("80C2000009D30782020181900181",
60 | mode=sim_router.INJECT_WITH_FORWARD)
61 | sw1, sw2, data = self.sendApdu("8012000026", mode=sim_router.INJECT_WITH_FORWARD)
62 | sw1, sw2, data = self.sendApdu("80140000108103022300020282818301008D020430",
63 | mode=sim_router.INJECT_WITH_FORWARD)
64 | sw1, sw2, data = self.sendApdu("801200002A", mode=sim_router.INJECT_WITH_FORWARD)
65 | sw1, sw2, data = self.sendApdu("80140000148103022300020282818301008D060430303"
66 | "13031", mode=sim_router.INJECT_WITH_FORWARD)
67 | sw1, sw2, data = self.sendApdu("801200001F", mode=sim_router.INJECT_WITH_FORWARD)
68 | #wait for post action finish in sat_ctrl
69 | time.sleep(3)
70 |
71 | def test_2_send_apdu(self):
72 | #self.simCtrl.selectAid()
73 | sw1, sw2, data = self.sendApdu("801000001EFFFFFFFF7F9F00DFFF00001FE2000000C3FB"
74 | "000700016800710000000018",
75 | mode=sim_router.INJECT_WITH_FORWARD)
76 | sw1, sw2, data = self.sendApdu("8012000076", mode=sim_router.INJECT_WITH_FORWARD)
77 | sw1, sw2, data = self.sendApdu("80C2000009D30782020181900185",
78 | mode=sim_router.INJECT_WITH_FORWARD)
79 | sw1, sw2, data = self.sendApdu("801200000B", mode=sim_router.INJECT_WITH_FORWARD)
80 | sw1, sw2, data = self.sendApdu("8014000017810305260002028281030106130962F060002"
81 | "093E00022", mode=sim_router.INJECT_WITH_FORWARD)
82 | sw1, sw2, data = self.sendApdu("8014000017810305260002028281030106130962F0600020"
83 | "93E00022", mode=sim_router.INJECT_WITH_FORWARD)
84 | #wait for post action finish in sat_ctrl
85 | time.sleep(3)
86 | #uncomment when fixed
87 | #self.shell.cd(".")
88 |
89 | @classmethod
90 | def tearDownClass(cls):
91 | cls.sim1.stop()
92 | cls.sim2.stop()
93 |
94 | if __name__ == "__main__":
95 | logging.basicConfig(level=logging.INFO, format='%(message)s')
96 | unittest.main()
97 |
--------------------------------------------------------------------------------
/tests/live/test_shell.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2015 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
7 |
8 | from sim import sim_router
9 | from sim import sim_card
10 | from sim import sim_shell
11 | from util import hextools
12 | from util import types_g
13 | from util import types
14 | import unittest
15 | import logging
16 |
17 | from sim import sim_reader
18 | from sim import sim_ctrl_2g
19 | from sim import sim_ctrl_3g
20 |
21 | MODE_SIM = sim_reader.MODE_PYSCARD
22 | SIM_TYPE = types.TYPE_USIM
23 |
24 | class TestSimShell(unittest.TestCase):
25 | @classmethod
26 | def setUpClass(cls):
27 | cls.simCard = sim_card.SimCard(mode=MODE_SIM)
28 | cls.simCard.removeAllReaders()
29 | try:
30 | cls.simCard.connect(0)
31 | except Exception as e:
32 | if "no card in reader" in str(e):
33 | cls.simCard.stop()
34 | raise Exception("No card in reader")
35 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=SIM_TYPE, mode=sim_router.SIMTRACE_OFFLINE)
36 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED)
37 | cls.shell = cls.simRouter.shell
38 |
39 | def test_01_read_iccid(self):
40 | status, data = self.shell.read("/2FE2")
41 | self.shell.assertOk(status, data)
42 | value = types.getDataValue(data)
43 | valueLength = len(value)
44 | self.assertGreater(valueLength, 9, "Invalid ICCID length")
45 |
46 | def test_02_read_imsi(self):
47 | path = "/ADF0/EF_IMSI"
48 | logging.info("test path: %s" %path)
49 | status, data1 = self.shell.readi(path)
50 | self.shell.assertOk(status, data1)
51 | path = "/7F20/EF_IMSI"
52 | logging.info("test path: %s" %path)
53 | status, data2 = self.shell.readi(path)
54 | self.shell.assertOk(status, data2)
55 | status, data = self.shell.cd("/")
56 | self.shell.assertOk(status, data)
57 | path = "EF_IMSI"
58 | logging.info("test path: %s" %path)
59 | status, data3 = self.shell.readi(path)
60 | self.shell.assertOk(status, data3)
61 | path = "/ADF_USIM"
62 | logging.info("test path: %s" %path)
63 | status, data = self.shell.cd(path)
64 | self.shell.assertOk(status, data)
65 | path = "./6F07"
66 | logging.info("test path: %s" %path)
67 | status, data4 = self.shell.readi(path)
68 | self.shell.assertOk(status, data4)
69 | self.assertEqual(data1, data2)
70 | self.assertEqual(data1, data3)
71 | self.assertEqual(data1, data4)
72 |
73 | def test_03_read_imsi_raw(self):
74 | status, data1 = self.shell.read("/ADF0/EF_IMSI")
75 | self.shell.assertOk(status, data1)
76 | status, data = self.shell.cd("/")
77 | self.shell.assertOk(status, data)
78 | status, data2 = self.shell.read("EF_IMSI")
79 | self.shell.assertOk(status, data2)
80 | imsi1 = types.getDataValue(data1)
81 | imsi2 = types.getDataValue(data2)
82 | self.assertEqual(imsi1, imsi2)
83 | imsiRawLength = len(imsi1)
84 | self.assertGreater(imsiRawLength, 14+3, "Invalid imsi raw length")
85 |
86 | def test_04_get_plmn(self):
87 | status, data1 = self.shell.get_plmn()
88 | self.shell.assertOk(status, data1)
89 | self.assertGreaterEqual(len(data1), 5*2)
90 | self.assertLessEqual(len(data1), 6*2)
91 | status, data2 = self.shell.readi("EF_IMSI")
92 | self.shell.assertOk(status, data2)
93 | self.assertTrue(data1 in data2)
94 |
95 | def test_05_read_arr(self):
96 | status, data = self.shell.read("/2F06")
97 | self.shell.assertOk(status, data)
98 |
99 | def test_06_create_file(self):
100 | # Create DF and EF using absolute paths
101 | dirPath = "/DEAD/"
102 | try:
103 | self.shell.delete(dirPath)
104 | except:
105 | pass
106 | status, out = self.shell.create(dirPath)
107 | self.shell.assertOk(status, out)
108 | status, out = self.shell.delete(dirPath)
109 | self.shell.assertOk(status, out)
110 |
111 | dirPath = "/ADF0/DEAD/"
112 | filePath = "/ADF0/DEAD/BEEF"
113 | try:
114 | self.shell.delete(dirPath)
115 | except:
116 | pass
117 | status, out = self.shell.create(dirPath)
118 | self.shell.assertOk(status, out)
119 | status, out = self.shell.create(filePath)
120 | self.shell.assertOk(status, out)
121 | status, out = self.shell.delete(filePath)
122 | self.shell.assertOk(status, out)
123 | status, out = self.shell.delete(dirPath)
124 | self.shell.assertOk(status, out)
125 |
126 | def test_07_create_file_relative(self):
127 | # Create DF and EF using relative paths
128 | dirPath = "./DEAD/"
129 | filePath = "./BEEF"
130 | dirPath2 = "./DEAF/"
131 | try:
132 | self.shell.delete(dirPath)
133 | except:
134 | pass
135 | status, out = self.shell.create(dirPath)
136 | self.shell.assertOk(status, out)
137 | status, out = self.shell.create(filePath)
138 | self.shell.assertOk(status, out)
139 | status, out = self.shell.create(dirPath2)
140 | self.shell.assertOk(status, out)
141 | status, out = self.shell.create(filePath)
142 | self.shell.assertOk(status, out)
143 |
144 | status, out = self.shell.delete(dirPath2)
145 | self.shell.assertOk(status, out)
146 | status, out = self.shell.delete(dirPath)
147 | self.shell.assertOk(status, out)
148 |
149 | def test_08_create_adf(self):
150 | # Get number of EF_DIR records
151 | status, data = self.shell.read("/2F00")
152 | self.shell.assertOk(status, data)
153 | numOfRecords = len(data.split(';')) - 1
154 | # Use the next free Id
155 | dirPath = "/ADF%d/" % numOfRecords
156 | try:
157 | self.shell.delete(dirPath)
158 | except:
159 | pass
160 | status, out = self.shell.create(dirPath)
161 | if status == "status NOK":
162 | raise unittest.SkipTest(
163 | """Known issue: ADF creation doesn't work for some SIM cards
164 | (INCORRECT_PARAMETER_IN_DATA_FIELD is returned)""")
165 | #self.shell.assertOk(status, out)
166 | status, out = self.shell.delete(dirPath)
167 | self.shell.assertOk(status, out)
168 |
169 | def test_09_pwd(self):
170 | dirPath = "/7F10/5F3A"
171 | name = "DF_PHONEBOOK"
172 | status, out = self.shell.cd(dirPath)
173 | self.shell.assertOk(status, out)
174 | status, out = self.shell.pwd()
175 | self.shell.assertOk(status, out)
176 | path = types.getDataValue(out)
177 | #compare to directory
178 | self.assertEqual(path, "path=%s/,name=%s,simId=0" %(dirPath, name))
179 |
180 | def test_10_ls(self):
181 | dirPath = "/"
182 | status, out = self.shell.cd(dirPath)
183 | self.shell.assertOk(status, out)
184 | status, out = self.shell.ls()
185 | self.shell.assertOk(status, out)
186 | files = types.getDataValue(out)
187 | self.assertTrue(files)
188 | dirPath = "/7F10"
189 | status, out = self.shell.cd(dirPath)
190 | self.shell.assertOk(status, out)
191 | status, out = self.shell.ls()
192 | self.shell.assertOk(status, out)
193 | files = types.getDataValue(out)
194 | self.assertTrue("5F3A/" in files, "Files: %s" %files)
195 |
196 | def test_11_resize(self):
197 | filePath = "/ADF0/DEAD/BEEF"
198 | parentPath = types.parentDirFromPath(filePath) + '/'
199 | # Cleanup
200 | try:
201 | self.shell.delete(parentPath)
202 | except:
203 | pass
204 | # Create temporary dir and file (linear)
205 | fileType = types_g.fileDescriptor.LINEAR_FIXED_STRUCTURE
206 | fileSize = 0x30
207 | recordLength = 0x10
208 | status, out = self.shell.create(filePath,
209 | "fileType=%X,fileSize=%X,recordLength=%X" \
210 | % (fileType, fileSize, recordLength))
211 | self.shell.assertOk(status, out)
212 | # Increase the size of the file (by 2 new records) with a pattern
213 | newFileSize = fileSize + recordLength * 2
214 | pattern = types.addTrailingBytes('', 0xA5, recordLength-4) # not the whole record length
215 | status, out = self.shell.resize(filePath, hex(newFileSize), pattern)
216 | self.shell.assertOk(status, out)
217 | # Check the data after resize
218 | status, data = self.shell.read(filePath)
219 | self.shell.assertOk(status, data)
220 | value = types.getDataValue(data).replace(';', '')
221 | self.assertEqual(len(value)/2, newFileSize)
222 | # Decrease the size of the file to one record
223 | status, out = self.shell.resize(filePath, hex(recordLength))
224 | self.shell.assertOk(status, out)
225 |
226 | status, out = self.shell.delete(parentPath)
227 | self.shell.assertOk(status, out)
228 |
229 | @unittest.skip("The EXTEND command is probably only supported by Gemalto")
230 | def test_12_extend(self):
231 | filePath = "/ADF0/DEAD/BEEF"
232 | parentPath = types.parentDirFromPath(filePath) + '/'
233 | # Cleanup
234 | try:
235 | self.shell.delete(parentPath)
236 | except:
237 | pass
238 | # Create temporary dir and file (linear)
239 | fileType = types_g.fileDescriptor.LINEAR_FIXED_STRUCTURE
240 | fileSize = 0x30
241 | recordLength = 0x10
242 | status, out = self.shell.create(filePath,
243 | "fileType=%X,fileSize=%X,recordLength=%X" \
244 | % (fileType, fileSize, recordLength))
245 | self.shell.assertOk(status, out)
246 | # Increase the size of the file (by 2 new records) with a pattern
247 | numOfRecordsToExtend = 2
248 | status, out = self.shell.extend(filePath, numOfRecordsToExtend)
249 | self.shell.assertOk(status, out)
250 | # Check the data after extension
251 | status, data = self.shell.read(filePath)
252 | self.shell.assertOk(status, data)
253 | value = types.getDataValue(data).replace(';', '')
254 | self.assertEqual(len(value)/2, fileSize + numOfRecordsToExtend * recordLength)
255 |
256 | status, out = self.shell.delete(parentPath)
257 | self.shell.assertOk(status, out)
258 |
259 | @classmethod
260 | def tearDownClass(cls):
261 | cls.simCard.stop()
262 |
263 | if __name__ == "__main__":
264 | logging.basicConfig(level=logging.INFO, format='%(message)s')
265 | unittest.main()
--------------------------------------------------------------------------------
/tests/live/test_sim.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
7 |
8 | from sim import sim_router
9 | from sim import sim_card
10 | from util import hextools
11 | from util import types_g
12 | from util import types
13 | import unittest
14 | import logging
15 |
16 | from sim import sim_reader
17 | from sim import sim_ctrl_2g
18 | from sim import sim_ctrl_3g
19 |
20 | MODE_SIM = sim_reader.MODE_PYSCARD
21 |
22 | PIN_1 = "1111"
23 | PUK_1 = "11111111"
24 |
25 | PIN_1_FALSE = "1112"
26 | PUK_1_FALSE = "11111112"
27 |
28 | class TestSim(unittest.TestCase):
29 | @classmethod
30 | def setUpClass(cls):
31 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=types.TYPE_USIM)
32 | cls.simCard.removeAllReaders()
33 | try:
34 | cls.simCard.connect(0)
35 | except Exception as e:
36 | if "no card in reader" in str(e):
37 | cls.simCard.stop()
38 | raise Exception("No card in reader")
39 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=types.TYPE_USIM, mode=sim_router.SIMTRACE_OFFLINE)
40 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED)
41 | cls.simCtrl = cls.simRouter.simCtrl
42 | cls.sendApdu = cls.simRouter.simCtrl.sendApdu
43 |
44 | def test_1_getAtr(self):
45 | atr = self.simCard.getATR()
46 | logging.info("ATR: %s" %hextools.bytes2hex(atr))
47 | self.assertGreater(len(atr), 4)
48 |
49 | def test_2_select_file(self):
50 | #SELECT_AID
51 | self.simCtrl.selectAid()
52 | #SELECT_FILE MF
53 | sw1, sw2, data = self.sendApdu("00A40004023F00")
54 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
55 | self.assertGreater(sw2, 2)
56 | length = sw2
57 | #GET_RESPONSE
58 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length)
59 |
60 | #SELECT_FILE EF_ICCID
61 | sw1, sw2, data = self.sendApdu("00A40004022FE2")
62 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
63 | length = sw2
64 | #GET_RESPONSE
65 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length)
66 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
67 |
68 | #SELECT_FILE EF_ICCID by path from MF
69 | sw1, sw2, data = self.sendApdu("00A40804022FE2")
70 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
71 | length = sw2
72 | #GET_RESPONSE
73 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length)
74 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
75 |
76 | #SELECT_FILE EF_IMSI by path from MF
77 | sw1, sw2, data = self.sendApdu("00A40804047FFF6F07")
78 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
79 | length = sw2
80 | #GET_RESPONSE
81 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length)
82 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
83 |
84 | def test_3_enable_pin(self):
85 | enabled = self.simCtrl.pin1Enabled()
86 | attemptsLeft = self.simCtrl.pin1Status()
87 | self.assertTrue(attemptsLeft, "PUK needed")
88 |
89 | if enabled:
90 | #disable pin
91 | sw1, sw2, data = self.sendApdu("0026000108%sFFFFFFFF" %PIN_1.encode("hex"))
92 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
93 |
94 | enabled = self.simCtrl.pin1Enabled()
95 | attemptsLeft = self.simCtrl.pin1Status()
96 | self.assertFalse(enabled)
97 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed")
98 |
99 | #enable pin
100 | sw1, sw2, data = self.sendApdu("0028000108%sFFFFFFFF" %PIN_1.encode("hex"))
101 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
102 | enabled = self.simCtrl.pin1Enabled()
103 | attemptsLeft = self.simCtrl.pin1Status()
104 | self.assertTrue(enabled)
105 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed")
106 |
107 | @unittest.skip("It's risky to block PIN1 on live SIM")
108 | def test_4_unblock_pin(self):
109 | enabled = self.simCtrl.pin1Enabled()
110 | attemptsLeft = self.simCtrl.pin1Status()
111 | self.assertTrue(enabled)
112 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed")
113 |
114 | #VERIFY
115 | sw1, sw2, data = self.sendApdu("0020000108%sFFFFFFFF" %PIN_1.encode("hex"))
116 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
117 | enabled = self.simCtrl.pin1Enabled()
118 | attemptsLeft = self.simCtrl.pin1Status()
119 | self.assertTrue(enabled)
120 |
121 | #block PIN
122 | for i in range(attemptsLeft):
123 | sw1, sw2, data = self.sendApdu("0020000108%sFFFFFFFF" %PIN_1_FALSE.encode("hex"))
124 | _attemptsLeft = attemptsLeft - i - 1
125 | types.assertSw(sw1, sw2, checkSw1='CODE_ATTEMPTS_LEFT', raiseException=True)
126 | left = self.simCtrl.pin1Status()
127 | self.assertEqual(left, _attemptsLeft)
128 |
129 | #unblock PIN
130 | enabled = self.simCtrl.pin1Enabled()
131 | attemptsLeft = self.simCtrl.pin1Status()
132 | self.assertFalse(attemptsLeft, "PIN is not blocked")
133 |
134 | attemptsLeft = self.simCtrl.pin1UnblockStatus()
135 | self.assertTrue(attemptsLeft>=10, "PUK1 attempts left: %d" %attemptsLeft)
136 | sw1, sw2, data = self.sendApdu("002C000110%s%sFFFFFFFF" %(PUK_1.encode("hex"), PIN_1.encode("hex")))
137 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
138 |
139 | def test_5_read_imsi(self):
140 | enabled = self.simCtrl.pin1Enabled()
141 | attemptsLeft = self.simCtrl.pin1Status()
142 | self.assertTrue(enabled)
143 | self.assertTrue(attemptsLeft, "PUK needed")
144 |
145 | self.simCtrl.selectAid()
146 | #SELECT_FILE IMSI
147 | sw1, sw2, data = self.sendApdu("00A40004026F07")
148 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
149 | length = sw2
150 | #GET_RESPONSE
151 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length)
152 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
153 |
154 | #READ BINARY
155 | tagData = types.parseFcpTlv(data, types.FILE_LENGTH_EXCLUDING_SI_TAG)
156 | if tagData == None:
157 | raise Exception("BINARY_LENGTH_TAG not found in FCI")
158 | imsiLength = tagData[1]
159 | sw1, sw2, data = self.sendApdu("00B00000%02X" %imsiLength)
160 | if types_g.sw[(sw1<<8) + sw2] == 'SECURITY_STATUS_NOT_SATISFIED':
161 | #VERIFY
162 | sw1, sw2, data = self.sendApdu("0020000108%sFFFFFFFF" %PIN_1.encode("hex"))
163 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
164 | #repeat READ BINARY
165 | sw1, sw2, data = self.sendApdu("00B00000%02X" %imsiLength)
166 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
167 | imsi = hextools.decode_BCD(data)[3:]
168 | logging.info("IMSI: %s" %imsi)
169 |
170 | def test_6_manage_channel(self):
171 | originChannel = 0
172 |
173 | """ Open first free channel """
174 | # MANAGE CHANNEL: OPEN (first free)
175 | sw1, sw2, data = self.sendApdu("0%01X7000%02X01" %(originChannel, 0))
176 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
177 | targetChannel = data[0] # 2
178 | # MANAGE CHANNEL: CLOSE
179 | sw1, sw2, data = self.sendApdu("0%01X7080%02X00" %(originChannel, targetChannel))
180 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
181 |
182 | """ Select on not open channel (when targetChannel = 0) """
183 | #SELECT_FILE MF
184 | sw1, sw2, data = self.sendApdu("0%01XA40004023F00" %targetChannel)
185 | # Two possible responses
186 | if types.assertSw(sw1, sw2, checkSw1='WRONG_INSTRUCTION_CLASS', raiseException=False):
187 | types.assertSw(sw1, sw2, checkSw='LOGICAL_CHANNEL_NOT_SUPPORTED', raiseException=True)
188 |
189 | """ Open, Select and Close channel """
190 | # MANAGE CHANNEL: OPEN (chosen)
191 | sw1, sw2, data = self.sendApdu("0%01X7000%02X00" %(originChannel, targetChannel))
192 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
193 |
194 | #SELECT_AID (ADF_USIM)
195 | # Select some AID (applet) on a specific channel.
196 | aid = self.simCtrl.getAid()
197 | apdu = "0%01XA40404%02X%s" %(targetChannel, len(aid)/2, aid)
198 | sw1, sw2, data = self.sendApdu(apdu)
199 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
200 | #SELECT_FILE MF
201 | sw1, sw2, data = self.sendApdu("0%01XA40004023F00" %targetChannel)
202 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
203 | self.assertGreater(sw2, 2)
204 | # MANAGE CHANNEL: CLOSE
205 | sw1, sw2, data = self.sendApdu("0%01X7080%02X00" %(originChannel, targetChannel))
206 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
207 |
208 | """ Open first free channel (when targetChannel = 0) """
209 | # MANAGE CHANNEL: OPEN (first free)
210 | sw1, sw2, data = self.sendApdu("0%01X7000%02X01" %(originChannel, 0))
211 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
212 | self.assertEqual(data[0], targetChannel)
213 |
214 | originChannel = data[0] # 1
215 | # MANAGE CHANNEL: OPEN (non-basic origin channel)
216 | sw1, sw2, data = self.sendApdu("0%01X7000%02X01" %(originChannel, 0))
217 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
218 | self.assertEqual(data[0], originChannel + 1)
219 | targetChannel = data[0] # 2
220 |
221 | """ Select MF on Non-Basic channel """
222 | #SELECT_AID (ADF_USIM)
223 | apdu = "0%01XA40404%02X%s" %(targetChannel, len(aid)/2, aid)
224 | sw1, sw2, data = self.sendApdu(apdu)
225 | #SELECT_FILE MF
226 | sw1, sw2, data = self.sendApdu("0%01XA40004023F00" %targetChannel)
227 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
228 | self.assertGreater(sw2, 2)
229 | """ Close Non-Basic channel """
230 | # MANAGE CHANNEL: CLOSE
231 | sw1, sw2, data = self.sendApdu("0%01X7080%02X00" %(originChannel, targetChannel))
232 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
233 |
234 | """ Select IMSI file on Basic channel """
235 | #SELECT_AID (ADF_USIM)
236 | #self.simCtrl.selectAid()
237 | apdu = "00A40404%02X%s" %(len(aid)/2, aid)
238 | sw1, sw2, data = self.sendApdu(apdu)
239 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
240 | #SELECT_FILE IMSI
241 | sw1, sw2, data = self.sendApdu("00A40004026F07")
242 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_3G', raiseException=True)
243 | length = sw2
244 | #GET_RESPONSE
245 | sw1, sw2, data = self.sendApdu("00C00000%02X" %length)
246 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
247 | tagData = types.parseFcpTlv(data, types.FILE_LENGTH_EXCLUDING_SI_TAG)
248 | if tagData == None:
249 | raise Exception("BINARY_LENGTH_TAG not found in FCI")
250 | imsiLength = tagData[1]
251 |
252 | """ Read IMSI file on Basic channel """
253 | #READ BINARY
254 | sw1, sw2, data = self.sendApdu("00B00000%02X" %imsiLength)
255 | if types_g.sw[(sw1<<8) + sw2] == 'SECURITY_STATUS_NOT_SATISFIED':
256 | #VERIFY
257 | sw1, sw2, data = self.sendApdu("0020000108%sFFFFFFFF" %PIN_1.encode("hex"))
258 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
259 | #repeat READ BINARY
260 | sw1, sw2, data = self.sendApdu("00B00000%02X" %imsiLength)
261 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
262 | imsi = hextools.decode_BCD(data)[3:]
263 | logging.info("IMSI: %s" %imsi)
264 |
265 | def tearDown(self):
266 | self.simCard.reset()
267 |
268 | @classmethod
269 | def tearDownClass(cls):
270 | cls.simCard.stop()
271 |
272 | if __name__ == "__main__":
273 | logging.basicConfig(level=logging.INFO, format='%(message)s')
274 | unittest.main()
--------------------------------------------------------------------------------
/tests/live/test_sim_2g.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
7 |
8 | from sim import sim_router
9 | from sim import sim_card
10 | from util import hextools
11 | from util import types_g
12 | from util import types
13 | import unittest
14 | import logging
15 |
16 | from sim import sim_reader
17 | from sim import sim_ctrl_2g
18 | from sim import sim_ctrl_3g
19 |
20 | MODE_SIM = sim_reader.MODE_PYSCARD
21 | SIM_TYPE = types.TYPE_SIM
22 |
23 | PIN_1 = "1111"
24 | PUK_1 = "11111111"
25 |
26 | PIN_1_FALSE = "1112"
27 | PUK_1_FALSE = "11111112"
28 |
29 | class TestSim(unittest.TestCase):
30 | @classmethod
31 | def setUpClass(cls):
32 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=SIM_TYPE)
33 | cls.simCard.removeAllReaders()
34 | try:
35 | cls.simCard.connect(0)
36 | except Exception as e:
37 | if "no card in reader" in str(e):
38 | cls.simCard.stop()
39 | raise Exception("No card in reader")
40 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=SIM_TYPE, mode=sim_router.SIMTRACE_OFFLINE)
41 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED)
42 | cls.simCtrl = cls.simRouter.simCtrl
43 | cls.sendApdu = cls.simRouter.simCtrl.sendApdu
44 |
45 | def test_1_getAtr(self):
46 | atr = self.simCard.getATR()
47 | logging.info("ATR: %s" %hextools.bytes2hex(atr))
48 | self.assertGreater(len(atr), 4)
49 |
50 | def test_2_select_file(self):
51 | #SELECT_FILE DF_GSM
52 | sw1, sw2, data = self.sendApdu("A0A40000027F20")
53 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
54 | length = sw2
55 | #GET_RESPONSE
56 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
57 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
58 |
59 | #SELECT_FILE MF
60 | sw1, sw2, data = self.sendApdu("A0A40000023F00")
61 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
62 | self.assertGreater(sw2, 2)
63 | length = sw2
64 | #GET_RESPONSE
65 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
66 |
67 | #SELECT_FILE EF_ICCID
68 | sw1, sw2, data = self.sendApdu("A0A40000022FE2")
69 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
70 | length = sw2
71 | #GET_RESPONSE
72 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
73 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
74 |
75 | def test_3_enable_pin(self):
76 | enabled = self.simCtrl.pin1Enabled()
77 | attemptsLeft = self.simCtrl.pin1Status()
78 | self.assertTrue(attemptsLeft, "PUK needed")
79 |
80 | if enabled:
81 | #disable pin
82 | sw1, sw2, data = self.sendApdu("A026000108%sFFFFFFFF" %PIN_1.encode("hex"))
83 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
84 |
85 | enabled = self.simCtrl.pin1Enabled()
86 | attemptsLeft = self.simCtrl.pin1Status()
87 | self.assertFalse(enabled)
88 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed")
89 |
90 | #enable pin
91 | sw1, sw2, data = self.sendApdu("A028000108%sFFFFFFFF" %PIN_1.encode("hex"))
92 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
93 | enabled = self.simCtrl.pin1Enabled()
94 | attemptsLeft = self.simCtrl.pin1Status()
95 | self.assertTrue(enabled)
96 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed")
97 |
98 | @unittest.skip("It's risky to block PIN1 on live SIM")
99 | def test_4_unblock_pin(self):
100 | enabled = self.simCtrl.pin1Enabled()
101 | attemptsLeft = self.simCtrl.pin1Status()
102 | self.assertTrue(enabled)
103 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed")
104 |
105 | #VERIFY
106 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex"))
107 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
108 | enabled = self.simCtrl.pin1Enabled()
109 | attemptsLeft = self.simCtrl.pin1Status()
110 | self.assertTrue(enabled)
111 |
112 | #block PIN
113 | for i in range(attemptsLeft):
114 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1_FALSE.encode("hex"))
115 |
116 | _attemptsLeft = attemptsLeft - i - 1
117 | if _attemptsLeft:
118 | types.assertSw(sw1, sw2, checkSw='GSM_ACCESS_CONDITION_NOT_FULFILLED', raiseException=True)
119 | else:
120 | types.assertSw(sw1, sw2, checkSw='GSM_UNSUCCESSFUL_USER_PIN_VERIFICATION', raiseException=True)
121 | left = self.simCtrl.pin1Status()
122 | self.assertEqual(left, _attemptsLeft)
123 |
124 | #unblock PIN
125 | enabled = self.simCtrl.pin1Enabled()
126 | attemptsLeft = self.simCtrl.pin1Status()
127 | self.assertFalse(attemptsLeft, "PIN is not blocked")
128 |
129 | attemptsLeft = self.simCtrl.pin1UnblockStatus()
130 | self.assertTrue(attemptsLeft>=10, "PUK1 attempts left: %d" %attemptsLeft)
131 | sw1, sw2, data = self.sendApdu("A02C000010%s%sFFFFFFFF" %(PUK_1.encode("hex"), PIN_1.encode("hex")))
132 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
133 |
134 | def test_5_read_imsi(self):
135 | enabled = self.simCtrl.pin1Enabled()
136 | attemptsLeft = self.simCtrl.pin1Status()
137 | self.assertTrue(enabled)
138 | self.assertTrue(attemptsLeft, "PUK needed")
139 |
140 | #SELECT_FILE DF_GSM
141 | sw1, sw2, data = self.sendApdu("A0A40000027F20")
142 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
143 | length = sw2
144 | #GET_RESPONSE
145 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
146 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
147 |
148 | #SELECT_FILE IMSI
149 | sw1, sw2, data = self.sendApdu("A0A40000026F07")
150 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
151 | length = sw2
152 | #GET_RESPONSE
153 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
154 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
155 |
156 | #READ BINARY
157 | length = data[3]
158 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length)
159 | if types_g.sw[(sw1<<8) + sw2] == 'GSM_ACCESS_CONDITION_NOT_FULFILLED':
160 | #VERIFY
161 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex"))
162 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
163 | #repeat READ BINARY
164 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length)
165 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
166 | imsi = hextools.decode_BCD(data)[3:]
167 | logging.info("IMSI: %s" %imsi)
168 |
169 | @unittest.skip("Might be not supported on 2G SIM")
170 | def test_6_manage_channel(self):
171 | originChannel = 0
172 | targetChannel = 2
173 |
174 | """ Select on not open channel """
175 | #SELECT_FILE MF
176 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel)
177 | types.assertSw(sw1, sw2, checkSw='LOGICAL_CHANNEL_NOT_SUPPORTED', raiseException=True)
178 |
179 | """ Open, Select and Close channel """
180 | # MANAGE CHANNEL: OPEN (chosen)
181 | sw1, sw2, data = self.sendApdu("A%01X7000%02X00" %(originChannel, targetChannel))
182 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
183 | #SELECT_FILE MF
184 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel)
185 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
186 | self.assertGreater(sw2, 2)
187 | # MANAGE CHANNEL: CLOSE
188 | sw1, sw2, data = self.sendApdu("A%01X7080%02X00" %(originChannel, targetChannel))
189 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
190 |
191 | """ Open first free channel (when targetChannel = 0) """
192 | # MANAGE CHANNEL: OPEN (first free)
193 | sw1, sw2, data = self.sendApdu("A%01X7000%02X01" %(originChannel, 0))
194 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
195 | self.assertEqual(data[0], 1)
196 |
197 | originChannel = data[0] # 1
198 | # MANAGE CHANNEL: OPEN (non-basic origin channel)
199 | sw1, sw2, data = self.sendApdu("A%01X7000%02X01" %(originChannel, 0))
200 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
201 | self.assertEqual(data[0], originChannel + 1)
202 | targetChannel = data[0] # 2
203 |
204 | """ Select IMSI file on Basic channel """
205 | #SELECT_FILE DF_GSM
206 | sw1, sw2, data = self.sendApdu("A0A40000027F20")
207 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
208 | #SELECT_FILE IMSI
209 | sw1, sw2, data = self.sendApdu("A0A40000026F07")
210 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
211 | length = sw2
212 | #GET_RESPONSE
213 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
214 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
215 | imsiLen = data[3]
216 | """ Select MF on Non-Basic channel """
217 | #SELECT_FILE MF
218 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel)
219 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
220 | self.assertGreater(sw2, 2)
221 | """ Read IMSI file on Basic channel """
222 | #READ BINARY
223 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %imsiLen)
224 | if types_g.sw[(sw1<<8) + sw2] == 'GSM_ACCESS_CONDITION_NOT_FULFILLED':
225 | #VERIFY
226 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex"))
227 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
228 | #repeat READ BINARY
229 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length)
230 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
231 | imsi = hextools.decode_BCD(data)[3:]
232 | logging.info("IMSI: %s" %imsi)
233 |
234 | """ Close Non-Basic channel """
235 | # MANAGE CHANNEL: CLOSE
236 | sw1, sw2, data = self.sendApdu("A%01X7080%02X00" %(originChannel, targetChannel))
237 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
238 |
239 | def tearDown(self):
240 | self.simCard.reset()
241 |
242 | @classmethod
243 | def tearDownClass(cls):
244 | cls.simCard.stop()
245 |
246 | if __name__ == "__main__":
247 | logging.basicConfig(level=logging.INFO, format='%(message)s')
248 | unittest.main()
--------------------------------------------------------------------------------
/tests/runner.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2015 Kamil Wartanowicz
4 |
5 | import os
6 | import sys
7 | sys.path.append(os.path.join(os.path.dirname(__file__), '../'))
8 | import unittest
9 | import logging
10 | import traceback
11 | from optparse import OptionParser
12 |
13 | import html_test_runner
14 | from sim import sim_router
15 |
16 | dir = os.path.dirname(__file__)
17 | resultDir = os.path.join(dir, "result")
18 | startDir = dir
19 |
20 | def setupLogger(loggingLevel):
21 | if not os.path.exists(resultDir):
22 | os.makedirs(resultDir)
23 | resultFile = os.path.join(resultDir, "result.txt")
24 | # create logger with 'root'
25 | logger = logging.getLogger()
26 | logger.handlers = []
27 | logger.setLevel(loggingLevel)
28 | # create file handler which logs even debug messages
29 | fileHandler = logging.FileHandler(resultFile)
30 | fileHandler.setLevel(loggingLevel)
31 | # create console handler with a higher log level
32 | consoleHandler = logging.StreamHandler()
33 | consoleHandler.setLevel(loggingLevel)
34 | # create ext handler with a higher log level
35 | # use this handler to redirect streaming in html_test_runner
36 | extHandler = logging.StreamHandler(stream=html_test_runner.stdout_redirector)
37 | extHandler.setLevel(loggingLevel)
38 | # create formatter and add it to the handlers
39 | formatterConsole = logging.Formatter(fmt='%(message)s')
40 | formatter = logging.Formatter(fmt='%(asctime)s %(message)s', datefmt='%H:%M:%S')
41 | consoleHandler.setFormatter(formatterConsole)
42 | fileHandler.setFormatter(formatter)
43 | extHandler.setFormatter(formatter)
44 | # add the handlers to the logger
45 | logger.addHandler(fileHandler)
46 | logger.addHandler(consoleHandler)
47 | logger.addHandler(extHandler)
48 | sim_router.setLoggerExtHandler(extHandler)
49 |
50 | def startFile(outfile):
51 | sysName = sys.platform
52 | if sysName.startswith('java'):
53 | import java.lang.System
54 | sysName = java.lang.System.getProperty('os.name').lower()
55 | if sysName.startswith('linux'):
56 | osName = "linux"
57 | elif sysName.startswith('win'):
58 | osName = "windows"
59 | if osName == "linux":
60 | cmd = "/usr/bin/xdg-open "
61 | elif osName == "windows":
62 | cmd = "cmd /c START "
63 | os.system(cmd + outfile.name)
64 |
65 | if __name__ == '__main__':
66 | parser = OptionParser()
67 | parser.add_option("-l", "--logging-level", dest="logging_level",
68 | help="Logging level, DEBUG=10, INFO=20, WARNING=30, ERROR=40")
69 | parser.add_option("-s", "--start-directory", dest="start_dir",
70 | help="Directory to start discovery")
71 | parser.add_option("-p", "--pattern", dest="pattern",
72 | help="Pattern to match test files (test*.py default)")
73 | parser.add_option("-m", "--module-class-test", dest="module_class_test",
74 | help="Pattern to match specific test/class: module.class.test")
75 | parser.add_option("-t", "--top-level-directory", dest="top_level_dir",
76 | help="Top level directory of project (defaults to start directory)")
77 | parser.add_option("-i", "--iterations", dest="iterations",
78 | help="Number of iterations")
79 | (options, args) = parser.parse_args()
80 |
81 | dictArgs = {}
82 | if options.logging_level:
83 | loggingLevel = int(options.logging_level)
84 | else:
85 | loggingLevel = logging.INFO
86 | if options.start_dir:
87 | startDir = options.start_dir
88 | dictArgs.update({'start_dir':startDir})
89 | if options.pattern:
90 | dictArgs.update({'pattern':options.pattern})
91 | if options.top_level_dir:
92 | dictArgs.update({'top_level_dir':options.top_level_dir})
93 |
94 | if options.iterations:
95 | nbrOfiterations = int(options.iterations)
96 | else:
97 | nbrOfiterations = 1
98 |
99 | moduleClassTests = []
100 | if options.module_class_test:
101 | moduleClassTests = options.module_class_test.split()
102 | #prevent creating *.pyc files
103 | sys.dont_write_bytecode = True
104 |
105 | while True:
106 | setupLogger(loggingLevel)
107 | infoText = "simLAB test runner: %s" %(" ".join(sys.argv[1:]))
108 | logging.info(infoText)
109 | loader = unittest.TestLoader()
110 | suites = loader.discover(**dictArgs)
111 | newSuite = unittest.TestSuite()
112 |
113 | for suite in suites._tests:
114 | new_suite = unittest.TestSuite()
115 | for _test_group in suite._tests:
116 | test_group = []
117 | try:
118 | iter(_test_group)
119 | test_group = _test_group
120 | except TypeError:
121 | # not iterable, probably module import failure
122 | logging.error("Import Failure? class="+_test_group.__class__.__name__+" test="+_test_group._testMethodName)
123 | test_group.append(_test_group)
124 | for test in test_group:
125 | if options.module_class_test:
126 | # check if any test matches expresion included with '-m' option
127 | moduleName = test.__class__.__module__
128 | className = test.__class__.__name__
129 | testMethodName = test._testMethodName
130 | nbrOfModules = len(moduleName.split("."))
131 |
132 | for moduleClassTest in moduleClassTests:
133 | classNameTmp = None
134 | testMethodNameTmp = None
135 | nbrOfPartsTmp = len(moduleClassTest.split("."))
136 | moduleNameTmp = ".".join(moduleClassTest.split(".")[0:nbrOfModules])
137 | if nbrOfPartsTmp > nbrOfModules:
138 | classNameTmp = moduleClassTest.split(".")[nbrOfModules]
139 | else:
140 | className = None
141 | if nbrOfPartsTmp > nbrOfModules + 1:
142 | testMethodNameTmp = moduleClassTest.split(".")[nbrOfModules+1]
143 | else:
144 | testMethodName = None
145 | if (moduleNameTmp == moduleName and
146 | classNameTmp == className and
147 | testMethodNameTmp == testMethodName):
148 | newSuite.addTest(test)
149 | else:
150 | #add test to suite in case of import error
151 | if className == 'ModuleImportFailure' and moduleClassTest[0] == test._testMethodName:
152 | newSuite.addTest(test)
153 | continue
154 | newSuite.addTest(test)
155 |
156 | outfile = open(os.path.join(resultDir, "report.html"), "w")
157 | testRunner = html_test_runner.HTMLTestRunner(
158 | stream=outfile,
159 | verbosity=2,
160 | title='Test Report',
161 | description=infoText,
162 | )
163 | testRunner.run(newSuite)
164 | outfile.close()
165 | startFile(outfile)
166 |
167 | nbrOfiterations -= 1
168 | if nbrOfiterations > 0:
169 | removeModuleSuite(suites)
170 | continue
171 | break
172 |
--------------------------------------------------------------------------------
/tests/soft/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/tests/soft/__init__.py
--------------------------------------------------------------------------------
/tests/soft/test_authentication.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2015 Kamil Wartanowicz
4 | # (c) 2015 Mikolaj Bartnicki
5 |
6 | import sys
7 | import os.path
8 | import logging
9 | import unittest
10 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
11 | from sim_soft import sim_auth
12 |
13 | class TestAuthentication(unittest.TestCase):
14 | def test_1_dummy_xor(self):
15 | # input:
16 | key = "00112233445566778899AABBCCDDEEFF"
17 | rand = "31323131353836343132313135383634"
18 | sqn = "000000000000"
19 | amf = "0000"
20 |
21 | # expected output
22 | res = "31231302716D5043B9AB9B8AF9E5D8CB"
23 | ck = "231302716D5043B9AB9B8AF9E5D8CB31"
24 | ik = "1302716D5043B9AB9B8AF9E5D8CB3123"
25 | kc = "0000000000000000"
26 | autn = "02716D5043B9000031231302716D5043"
27 |
28 | logging.info("Codes to check:\n"
29 | "key=%s\n"
30 | "rand=%s\n"
31 | "sqn=%s\n"
32 | "amf=%s\n"
33 | %(key, rand, sqn, amf))
34 | result = sim_auth.dummyXorHex(key, rand, sqn, amf, None)
35 |
36 | self.assertEqual(result['res'], res)
37 | self.assertEqual(result['ck'], ck)
38 | self.assertEqual(result['ik'], ik)
39 | self.assertEqual(result['kc'], kc)
40 | self.assertEqual(result['autn'], autn)
41 |
42 | def test_2_dummy_xor(self):
43 | # input:
44 | key = "00112233445566778899AABBCCDDEEFF"
45 | rand = "41ec50d284b5284fb9a317e9f089f247"
46 | sqn = "FFFFFFFFFFFF"
47 | amf = "8000"
48 |
49 | # expected output
50 | autn = "1E3F1FB1C7CE8000BE028D1E3F1FCE38"
51 | res = "41FD72E1C0E04E38313ABD523C541CB8"
52 | ck = "FD72E1C0E04E38313ABD523C541CB841"
53 | ik = "72E1C0E04E38313ABD523C541CB841FD"
54 | kc = "087C4F48E6D2F0B7"
55 |
56 | logging.info("Codes to check:\n"
57 | "key=%s\n"
58 | "rand=%s\n"
59 | "sqn=%s\n"
60 | "amf=%s\n"
61 | %(key, rand, sqn, amf))
62 | result = sim_auth.dummyXorHex(key, rand, sqn, amf, None)
63 |
64 | self.assertEqual(result['res'], res)
65 | self.assertEqual(result['ck'], ck)
66 | self.assertEqual(result['ik'], ik)
67 | self.assertEqual(result['kc'], kc)
68 | self.assertEqual(result['autn'], autn)
69 |
70 | def test_3_dummy_xor_data(self):
71 | # input:
72 | key = "00112233445566778899AABBCCDDEEFF"
73 | rand = "0123456789ABCDEF0123456789ABCDEF"
74 | sqn = "000000000000"
75 | amf = "0000"
76 | autn = "54CDFEAB9889000001326754CDFEAB98"
77 |
78 | logging.info("Codes to check:\n"
79 | "key=%s\n"
80 | "rand=%s\n"
81 | "sqn=%s\n"
82 | "amf=%s\n"
83 | %(key, rand, sqn, amf))
84 |
85 | result = sim_auth.dummyXorData(key, rand, sqn, amf, autn)
86 | self.assertEqual(result,
87 | "DB" #OK
88 | "1001326754CDFEAB9889BAEFDC45762310" #RES
89 | "10326754CDFEAB9889BAEFDC4576231001" #CK
90 | "106754CDFEAB9889BAEFDC457623100132" #IK
91 | "080000000000000000") #KC
92 |
93 | def test_4_dummy_xor_data(self):
94 | '''TODO: implement sqn range check'''
95 | #apdu 00880081221041EC50D284B5284FB9A317E9F089F24710E1C0E04E3822800041FD72E1C0F3CE38
96 | # input:
97 | key = "00112233445566778899AABBCCDDEEFF"
98 | rand = "41ec50d284b5284fb9a317e9f089f247"
99 | sqn = "000000000000"
100 | amf = "8000"
101 | autn = "1E3F1FB1C7CE8000BE028D1E3F1FCE38"
102 |
103 | logging.info("Codes to check:\n"
104 | "key=%s\n"
105 | "rand=%s\n"
106 | "sqn=%s\n"
107 | "amf=%s\n"
108 | %(key, rand, sqn, amf))
109 |
110 | result = sim_auth.dummyXorData(key, rand, sqn, amf, autn)
111 | self.assertEqual(result,
112 | "DB" #OK
113 | "1041FD72E1C0E04E38313ABD523C541CB8" #RES
114 | "10FD72E1C0E04E38313ABD523C541CB841" #CK
115 | "1072E1C0E04E38313ABD523C541CB841FD" #IK
116 | "08087C4F48E6D2F0B7") #KC
117 |
118 | if __name__ == "__main__":
119 | logging.basicConfig(level=logging.INFO, format='%(message)s')
120 | unittest.main()
--------------------------------------------------------------------------------
/tests/soft/test_ext_control_dbus.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2016 Janusz Kuszczynski
4 |
5 | import unittest
6 | import logging
7 | import os
8 | import shlex
9 | import signal
10 | import subprocess
11 | import threading
12 | import time
13 | import sys
14 | import os.path
15 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
16 | from util import types
17 |
18 | DBUS_ENABLE = True
19 | if os.name != 'posix':
20 | DBUS_ENABLE = False
21 | if DBUS_ENABLE:
22 | import dbus
23 |
24 |
25 | @unittest.skipUnless(DBUS_ENABLE, "Windows doesn't support DBUS")
26 | class TestDBus(unittest.TestCase):
27 | @classmethod
28 | def setUpClass(cls):
29 | cls.server = SimlabServerThread()
30 | cls.server.setDaemon(True)
31 | cls.server.start()
32 | time.sleep(1)
33 | cls.bus = dbus.SessionBus()
34 | cls.session = cls.bus.get_object("org.sim.simlab", "/org/sim/simlab")
35 | cls.interface = dbus.Interface(cls.session, "org.sim.simlab")
36 |
37 | def test_1_pwd(self):
38 | result = self.interface.pwd()
39 | self.assertStatus(result)
40 |
41 | def test_2_long_command(self):
42 | self.interface.cd('/')
43 | result = self.interface.ls()
44 | self.assertStatus(result)
45 |
46 | def test_3_get_plmn(self):
47 | result = self.interface.get_plmn()
48 | self.assertStatus(result)
49 |
50 | def assertStatus(self, status):
51 | if 'status OK' in status:
52 | logging.info('Test OK')
53 | else:
54 | logging.warning("".join(status))
55 | self.fail(status)
56 |
57 | @classmethod
58 | def tearDownClass(cls):
59 | cls.server.close()
60 |
61 | class SimlabServerThread(threading.Thread):
62 | def __init__(self):
63 | threading.Thread.__init__(self)
64 | threading.Thread.setName(self, 'SimlabServerThread')
65 | self.proc = None
66 | self.__lock = threading.Lock()
67 |
68 | def run(self):
69 | self.__lock.acquire()
70 | # Can't run plac in thread thus script is loaded from external script
71 | scriptPath = os.path.abspath(os.path.dirname(__file__))
72 | scriptPath = os.path.join(scriptPath, "../../mim/mim_tmp.py").replace("\\", "/")
73 | scriptFile = open(scriptPath, "w")
74 | scriptFile.write(script)
75 | scriptFile.close()
76 | scriptCmd = "python " + scriptPath
77 | self.proc = subprocess.Popen(shlex.split(scriptCmd))
78 | self.__lock.release()
79 |
80 | def close(self):
81 | types.killProcess("mim_tmp.py")
82 |
83 | script = '''
84 | import sys,os.path
85 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
86 | import logging
87 | from sim import sim_router
88 | from sim import sim_card
89 | from sim import sim_reader
90 | from util import types
91 | logging.basicConfig(level=logging.INFO, format='%(message)s')
92 | simType=types.TYPE_USIM
93 | simCard = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType)
94 | simCard.removeAllReaders()
95 | simCard.connect(sim_reader.READER_ID_0)
96 | simRouter = sim_router.SimRouter(cards=[simCard],
97 | atr=None,
98 | type=simType,
99 | mode=sim_router.SIMTRACE_OFFLINE)
100 | simRouter.run(mode=sim_router.ROUTER_MODE_DBUS)
101 | '''
102 |
103 | if __name__ == "__main__":
104 | logging.basicConfig(level=logging.INFO, format='%(message)s')
105 | unittest.main()
106 |
107 |
--------------------------------------------------------------------------------
/tests/soft/test_ext_control_telnet.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2016 Kamil Wartanowicz
4 | import unittest
5 | import logging
6 | import os
7 | import re
8 | import shlex
9 | import signal
10 | import subprocess
11 | import sys
12 | import telnetlib
13 | import threading
14 | import time
15 | import os.path
16 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
17 | from util import types
18 |
19 | HOST = "localhost"
20 | prompt = "i> "
21 |
22 |
23 | class TestTelnet(unittest.TestCase):
24 | @classmethod
25 | def setUpClass(cls):
26 | cls.server = SimlabServerThread()
27 | cls.server.setDaemon(True)
28 | cls.server.start()
29 | time.sleep(3)
30 | cls.telnet = TelnetSimlab()
31 | cls.simLab = cls.telnet.send
32 |
33 | def test_1_pwd(self):
34 | status, data = self.simLab("pwd")
35 | self.assertTrue(status)
36 | logging.info(data)
37 | self.assertTrue(data)
38 |
39 | def test_2_long_command(self):
40 | self.simLab("cd ADF0")
41 | status, data = self.simLab("ls")
42 | self.assertTrue(status)
43 | logging.info(data)
44 | self.assertTrue(data)
45 |
46 | @classmethod
47 | def tearDownClass(cls):
48 | cls.telnet.close()
49 | cls.server.close()
50 |
51 | class SimlabServerThread(threading.Thread):
52 | def __init__(self):
53 | threading.Thread.__init__(self)
54 | threading.Thread.setName(self, 'SimlabServerThread')
55 | self.proc = None
56 | self.__lock = threading.Lock()
57 |
58 | def run(self):
59 | self.__lock.acquire()
60 | # Can't run plac in thread thus script is loaded from external script
61 | scriptPath = os.path.abspath(os.path.dirname(__file__))
62 | scriptPath = os.path.join(scriptPath, "../../mim/mim_tmp.py").replace("\\", "/")
63 | scriptFile = open(scriptPath, "w")
64 | scriptFile.write(script)
65 | scriptFile.close()
66 | scriptCmd = "python " + scriptPath
67 | self.proc = subprocess.Popen(shlex.split(scriptCmd))
68 | self.__lock.release()
69 |
70 | def close(self):
71 | types.killProcess("mim_tmp.py")
72 |
73 | class TelnetSimlab(object):
74 | def __init__(self, port=2199):
75 | self.tn = telnetlib.Telnet(HOST, port)
76 | # Read prompt
77 | self.tn.read_until(prompt, timeout=1)
78 |
79 | def clearReadBuffer(self):
80 | # Clear buffer
81 | self.tn.read_very_eager()
82 |
83 | def send(self, cmd):
84 | self.clearReadBuffer()
85 | self.tn.write(cmd + "\r\n")
86 | return self.getResponse()
87 |
88 | def validateResponse(self, out):
89 | if len(out.split("\n")) > 2:
90 | raise Exception("Response data not expected: " + out)
91 |
92 | def statusOk(self, out):
93 | self.validateResponse(out)
94 | statusRe = re.search("status (.*)", out)
95 | if not statusRe:
96 | raise Exception("Status data not expected: " + out)
97 | status = statusRe.group(1)
98 | if status == "OK":
99 | return True
100 | else:
101 | return False
102 |
103 | def getData(self, out):
104 | self.validateResponse(out)
105 | dataRe = re.search("data (.*)", out)
106 | if not dataRe:
107 | return None
108 | data = dataRe.group(1)
109 | return data
110 |
111 | def getResponse(self):
112 | out = self.tn.read_until(prompt, timeout=30)
113 | out = out.rstrip(prompt)
114 | out = out.replace("\r", "")
115 | out = out.rstrip("\n")
116 | status = self.statusOk(out)
117 | data = self.getData(out)
118 | return status, data
119 |
120 | def close(self):
121 | self.tn.close()
122 |
123 | script = '''
124 | import sys,os.path
125 | sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
126 | import logging
127 | from sim import sim_router
128 | from sim import sim_card
129 | from sim import sim_reader
130 | from util import types
131 | logging.basicConfig(level=logging.INFO, format='%(message)s')
132 | simType=types.TYPE_USIM
133 | simCard = sim_card.SimCard(mode=sim_reader.MODE_SIM_SOFT, type=simType)
134 | simCard.removeAllReaders()
135 | simCard.connect(sim_reader.READER_ID_0)
136 | simRouter = sim_router.SimRouter(cards=[simCard],
137 | atr=None,
138 | type=simType,
139 | mode=sim_router.SIMTRACE_OFFLINE)
140 | simRouter.run(mode=sim_router.ROUTER_MODE_TELNET)
141 | '''
142 |
143 | if __name__ == "__main__":
144 | logging.basicConfig(level=logging.INFO, format='%(message)s')
145 | unittest.main()
146 |
--------------------------------------------------------------------------------
/tests/soft/test_sat.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 | # (c) 2014 Szymon Mielczarek
5 |
6 | import sys
7 | import os.path
8 | import time
9 |
10 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
11 | from sim import sim_router
12 | from sim import sim_card
13 | from util import types
14 | import unittest
15 | import logging
16 |
17 | from sim import sim_reader
18 |
19 | MODE_SIM = sim_reader.MODE_SIM_SOFT
20 |
21 | PIN_1 = "1111"
22 | PUK_1 = "11111111"
23 |
24 | PIN_1_FALSE = "1112"
25 | PUK_1_FALSE = "11111112"
26 |
27 | class TestSimRouter(unittest.TestCase):
28 | @classmethod
29 | def setUpClass(cls):
30 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=types.TYPE_SIM)
31 | cls.simCard.removeAllReaders()
32 | try:
33 | cls.simCard.connect(0)
34 | except Exception as e:
35 | if "no card in reader" in str(e):
36 | cls.simCard.stop()
37 | raise Exception("No card in reader")
38 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=types.TYPE_SIM, mode=sim_router.SIMTRACE_OFFLINE)
39 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED)
40 | cls.simCtrl = cls.simRouter.simCtrl
41 | cls.sendApdu = cls.simRouter.simCtrl.sendApdu
42 |
43 | def test_1_create_setUpMenu(self):
44 | # create Set Up Menu
45 | sw1, sw2, data = self.sendApdu(
46 | "A01000001FFFFFFFFF7F0F00DF7F03071FE2080C0003000E000000000000000000000000")
47 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True)
48 | self.assertGreater(sw2, 2)
49 |
50 | def test_2_select_menu_item1(self):
51 | # envelope: MENU_SELECTION - Item1
52 | item = 0
53 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item))
54 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True)
55 | length = sw2
56 | # fetch: provideLocalInformation(local_info.IMEI_OF_THE_TERMINAL)
57 | sw1, sw2, data = self.sendApdu("A0120000%02X" %length)
58 | # send terminal response with IMEI: 00440245243851-2
59 | sw1, sw2, data = self.sendApdu(
60 | "A01400001681030226010202828103010014080A40044225345801")
61 |
62 | def test_3_select_menu_item2(self):
63 | # envelope: MENU_SELECTION - Item2
64 | item = 1
65 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item))
66 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True)
67 | length = sw2
68 | # fetch: GET_INPUT
69 | sw1, sw2, data = self.sendApdu("A0120000%02X" %length)
70 | # terminal response: input text
71 | sw1, sw2, data = self.sendApdu(
72 | "A0140000148103052301020282818301008D06043030313031")
73 | time.sleep(2)
74 |
75 | def test_4_select_menu_item3(self):
76 | # envelope: MENU_SELECTION - Item3
77 | item = 2
78 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item))
79 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True)
80 | length = sw2
81 | # fetch - PLAY_TONE
82 | sw1, sw2, data = self.sendApdu("A0120000%02X" %length)
83 | # terminal response
84 | sw1, sw2, data = self.sendApdu(
85 | "A01400000C810304200002028281830100")
86 |
87 | def test_5_select_menu_item4(self):
88 | # envelope: MENU_SELECTION - Item4
89 | item = 3
90 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item))
91 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True)
92 | length = sw2
93 | # fetch
94 | sw1, sw2, data = self.sendApdu("A0120000%02X" % length)
95 |
96 | # envelope: SELECT_ITEM - Item 1
97 | # terminal response
98 | sw1, sw2, data = self.sendApdu("A01400000F810302240002028281830100900180")
99 |
100 | def test_6_select_menu_item5(self):
101 | # envelope: MENU_SELECTION - Item5
102 | item = 4
103 | sw1, sw2, data = self.sendApdu("A0C2000009D3078202018190018" + str(item))
104 | types.assertSw(sw1, sw2, checkSw1='NO_ERROR_PROACTIVE_DATA', raiseException=True)
105 | length = sw2
106 | # fetch
107 | sw1, sw2, data = self.sendApdu("A0120000%02X" %length)
108 |
109 | @classmethod
110 | def tearDownClass(cls):
111 | time.sleep(1)
112 | cls.simCard.stop()
113 |
114 | if __name__ == "__main__":
115 | logging.basicConfig(level=logging.INFO, format='%(message)s')
116 | unittest.main()
--------------------------------------------------------------------------------
/tests/soft/test_shell.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2015 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
7 |
8 | from sim import sim_router
9 | from sim import sim_card
10 | from util import types_g
11 | from util import types
12 | import unittest
13 | import logging
14 |
15 | from sim import sim_reader
16 |
17 | MODE_SIM = sim_reader.MODE_SIM_SOFT
18 | SIM_TYPE = types.TYPE_USIM
19 |
20 |
21 | class TestSimShell(unittest.TestCase):
22 | @classmethod
23 | def setUpClass(cls):
24 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=SIM_TYPE)
25 | cls.simCard.removeAllReaders()
26 | try:
27 | cls.simCard.connect(0)
28 | except Exception as e:
29 | if "no card in reader" in str(e):
30 | cls.simCard.stop()
31 | raise Exception("No card in reader")
32 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=SIM_TYPE, mode=sim_router.SIMTRACE_OFFLINE)
33 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED)
34 | cls.shell = cls.simRouter.shell
35 |
36 | def test_01_read_iccid(self):
37 | status, data = self.shell.read("/2FE2")
38 | self.assertEqual(status, "status OK")
39 | value = types.getDataValue(data)
40 | self.assertTrue(status, "read imsi failed")
41 | valueLength = len(value)
42 | self.assertGreater(valueLength, 9, "Invalid ICCID length")
43 |
44 | def test_02_read_imsi(self):
45 | status, data1 = self.shell.readi("/ADF0/EF_IMSI")
46 | self.shell.assertOk(status, data1)
47 | status, data2 = self.shell.readi("/7F20/EF_IMSI")
48 | self.shell.assertOk(status, data2)
49 | status, data = self.shell.cd("/")
50 | self.shell.assertOk(status, data)
51 | status, data3 = self.shell.readi("EF_IMSI")
52 | self.shell.assertOk(status, data3)
53 | status, data = self.shell.cd("/ADF_USIM")
54 | self.shell.assertOk(status, data)
55 | status, data4 = self.shell.readi("./6F07")
56 | self.shell.assertOk(status, data4)
57 | self.assertEqual(data1, data2)
58 | self.assertEqual(data1, data3)
59 | self.assertEqual(data1, data4)
60 |
61 | def test_03_read_imsi_raw(self):
62 | status, data1 = self.shell.read("/ADF0/EF_IMSI")
63 | self.shell.assertOk(status, data1)
64 | status, data = self.shell.cd("/")
65 | self.shell.assertOk(status, data)
66 | status, data2 = self.shell.read("EF_IMSI")
67 | self.shell.assertOk(status, data2)
68 | imsi1 = types.getDataValue(data1)
69 | imsi2 = types.getDataValue(data2)
70 | self.assertEqual(imsi1, imsi2)
71 | imsiRawLength = len(imsi1)
72 | self.assertGreater(imsiRawLength, 14+3, "Invalid imsi raw length")
73 |
74 | def test_04_get_plmn(self):
75 | status, data1 = self.shell.get_plmn()
76 | self.shell.assertOk(status, data1)
77 | self.assertGreaterEqual(len(data1), 5*2)
78 | self.assertLessEqual(len(data1), 6*2)
79 | status, data2 = self.shell.readi("EF_IMSI")
80 | self.shell.assertOk(status, data2)
81 | self.assertTrue(data1 in data2)
82 |
83 | def test_05_read_arr(self):
84 | status, data = self.shell.read("/2F06")
85 | self.assertTrue(status, "read arr failed")
86 |
87 | def test_06_create_file(self):
88 | # Create DF and EF using absolute paths.
89 | dirPath = "/DEAD/"
90 | try:
91 | self.shell.delete(dirPath)
92 | except:
93 | pass
94 | status, out = self.shell.create(dirPath)
95 | self.shell.assertOk(status, out)
96 | status, out = self.shell.delete(dirPath)
97 | self.shell.assertOk(status, out)
98 |
99 | dirPath = "/ADF0/DEAD/"
100 | filePath = "/ADF0/DEAD/BEEF"
101 | try:
102 | self.shell.delete(dirPath)
103 | except:
104 | pass
105 | status, out = self.shell.create(dirPath)
106 | self.shell.assertOk(status, out)
107 | status, out = self.shell.create(filePath)
108 | self.shell.assertOk(status, out)
109 | status, out = self.shell.delete(filePath)
110 | self.shell.assertOk(status, out)
111 | status, out = self.shell.delete(dirPath)
112 | self.shell.assertOk(status, out)
113 |
114 | def test_07_create_file_relative(self):
115 | # Create DF and EF using relative paths.
116 | dirPath = "./DEAD/"
117 | filePath = "./BEEF"
118 | dirPath2 = "./DEAF/"
119 | try:
120 | self.shell.delete(dirPath)
121 | except:
122 | pass
123 | status, out = self.shell.create(dirPath)
124 | self.shell.assertOk(status, out)
125 | status, out = self.shell.create(filePath)
126 | self.shell.assertOk(status, out)
127 | status, out = self.shell.create(dirPath2)
128 | self.shell.assertOk(status, out)
129 | status, out = self.shell.create(filePath)
130 | self.shell.assertOk(status, out)
131 |
132 | status, out = self.shell.delete(dirPath2)
133 | self.shell.assertOk(status, out)
134 | status, out = self.shell.delete(dirPath)
135 | self.shell.assertOk(status, out)
136 |
137 | def test_08_create_adf(self):
138 | # Get number of EF_DIR records
139 | status, data = self.shell.read("/2F00")
140 | self.shell.assertOk(status, data)
141 | numOfRecords = len(data.split(';')) - 1
142 | # Use the next free Id
143 | dirPath = "/ADF%d/" % numOfRecords
144 | try:
145 | self.shell.delete(dirPath)
146 | except:
147 | pass
148 | status, out = self.shell.create(dirPath)
149 | self.shell.assertOk(status, out)
150 | status, out = self.shell.delete(dirPath)
151 | self.shell.assertOk(status, out)
152 |
153 | def test_09_pwd(self):
154 | dirPath = "/7F10/5F3A"
155 | name = "DF_PHONEBOOK"
156 | status, out = self.shell.cd(dirPath)
157 | self.shell.assertOk(status, out)
158 | status, out = self.shell.pwd()
159 | self.shell.assertOk(status, out)
160 | path = types.getDataValue(out)
161 | #compare to directory
162 | self.assertEqual(path, "path=%s/,name=%s,simId=0" %(dirPath, name))
163 |
164 | def test_10_ls(self):
165 | dirPath = "/"
166 | status, out = self.shell.cd(dirPath)
167 | self.shell.assertOk(status, out)
168 | status, out = self.shell.ls()
169 | self.shell.assertOk(status, out)
170 | files = types.getDataValue(out)
171 | self.assertTrue(files)
172 | dirPath = "/7F10"
173 | status, out = self.shell.cd(dirPath)
174 | self.shell.assertOk(status, out)
175 | status, out = self.shell.ls()
176 | self.shell.assertOk(status, out)
177 | files = types.getDataValue(out)
178 | self.assertTrue("5F3A/" in files, "Files: %s" %files)
179 |
180 | def test_11_resize(self):
181 | filePath = "/ADF0/DEAD/BEEF"
182 | parentPath = types.parentDirFromPath(filePath) + '/'
183 | # Cleanup
184 | try:
185 | self.shell.delete(parentPath)
186 | except:
187 | pass
188 | # Create temporary dir and file (linear)
189 | fileType = types_g.fileDescriptor.LINEAR_FIXED_STRUCTURE
190 | fileSize = 0x30
191 | recordLength = 0x10
192 | status, out = self.shell.create(filePath,
193 | "fileType=%X,fileSize=%X,recordLength=%X" \
194 | % (fileType, fileSize, recordLength))
195 | self.shell.assertOk(status, out)
196 | # Increase the size of the file (by 2 new records) with a pattern
197 | newFileSize = fileSize + recordLength * 2
198 | pattern = types.addTrailingBytes('', 0xA5, recordLength-4) # not the whole record length
199 | status, out = self.shell.resize(filePath, hex(newFileSize), pattern)
200 | self.shell.assertOk(status, out)
201 | # Check the data after resize
202 | status, data = self.shell.read(filePath)
203 | self.shell.assertOk(status, data)
204 | value = types.getDataValue(data).replace(';', '')
205 | self.assertEqual(len(value)/2, newFileSize)
206 | # Decrease the size of the file to one record
207 | status, out = self.shell.resize(filePath, hex(recordLength))
208 | self.shell.assertOk(status, out)
209 |
210 | status, out = self.shell.delete(parentPath)
211 | self.shell.assertOk(status, out)
212 |
213 | if __name__ == "__main__":
214 | logging.basicConfig(level=logging.INFO, format='%(message)s')
215 | unittest.main()
216 |
--------------------------------------------------------------------------------
/tests/soft/test_sim_2g.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | import sys,os.path
6 | sys.path.append(os.path.join(os.path.dirname(__file__), '../..'))
7 |
8 | from sim import sim_router
9 | from sim import sim_card
10 | from util import hextools
11 | from util import types_g
12 | from util import types
13 | import unittest
14 | import logging
15 |
16 | from sim import sim_reader
17 | from sim import sim_ctrl_2g
18 | from sim import sim_ctrl_3g
19 |
20 | MODE_SIM = sim_reader.MODE_SIM_SOFT
21 | SIM_TYPE = types.TYPE_SIM
22 |
23 | PIN_1 = "1111"
24 | PUK_1 = "11111111"
25 |
26 | PIN_1_FALSE = "1112"
27 | PUK_1_FALSE = "11111112"
28 |
29 | class TestSim(unittest.TestCase):
30 | @classmethod
31 | def setUpClass(cls):
32 | cls.simCard = sim_card.SimCard(mode=MODE_SIM, type=SIM_TYPE)
33 | cls.simCard.removeAllReaders()
34 | try:
35 | cls.simCard.connect(0)
36 | except Exception as e:
37 | if "no card in reader" in str(e):
38 | cls.simCard.stop()
39 | raise Exception("No card in reader")
40 | cls.simRouter = sim_router.SimRouter(cards=[cls.simCard], type=types.TYPE_SIM, mode=sim_router.SIMTRACE_OFFLINE)
41 | cls.simRouter.run(mode=sim_router.ROUTER_MODE_DISABLED)
42 | cls.simCtrl = cls.simRouter.simCtrl
43 | cls.sendApdu = cls.simCtrl.sendApdu
44 |
45 | def test_1_getAtr(self):
46 | atr = self.simCard.getATR()
47 | logging.info("ATR: %s" %hextools.bytes2hex(atr))
48 | self.assertGreater(len(atr), 4)
49 |
50 | def test_2_select_file(self):
51 | #SELECT_FILE DF_GSM
52 | sw1, sw2, data = self.sendApdu("A0A40000027F20")
53 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
54 | length = sw2
55 | #GET_RESPONSE
56 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
57 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
58 |
59 | #SELECT_FILE MF
60 | sw1, sw2, data = self.sendApdu("A0A40000023F00")
61 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
62 | self.assertGreater(sw2, 2)
63 | length = sw2
64 | #GET_RESPONSE
65 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
66 |
67 | #SELECT_FILE EF_ICCID
68 | sw1, sw2, data = self.sendApdu("A0A40000022FE2")
69 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
70 | length = sw2
71 | #GET_RESPONSE
72 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
73 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
74 |
75 | def test_3_enable_pin(self):
76 | enabled = self.simCtrl.pin1Enabled()
77 | attemptsLeft = self.simCtrl.pin1Status()
78 | self.assertTrue(attemptsLeft, "PUK needed")
79 |
80 | if enabled:
81 | #disable pin
82 | sw1, sw2, data = self.sendApdu("A026000108%sFFFFFFFF" %PIN_1.encode("hex"))
83 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
84 |
85 | enabled = self.simCtrl.pin1Enabled()
86 | attemptsLeft = self.simCtrl.pin1Status()
87 | self.assertFalse(enabled)
88 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed")
89 |
90 | #enable pin
91 | sw1, sw2, data = self.sendApdu("A028000108%sFFFFFFFF" %PIN_1.encode("hex"))
92 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
93 | enabled = self.simCtrl.pin1Enabled()
94 | attemptsLeft = self.simCtrl.pin1Status()
95 | self.assertTrue(enabled)
96 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed")
97 |
98 | def test_4_unblock_pin(self):
99 | enabled = self.simCtrl.pin1Enabled()
100 | attemptsLeft = self.simCtrl.pin1Status()
101 | self.assertTrue(enabled)
102 | self.assertTrue(attemptsLeft, "No attempts left. PUK needed")
103 |
104 | #VERIFY
105 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex"))
106 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
107 | enabled = self.simCtrl.pin1Enabled()
108 | attemptsLeft = self.simCtrl.pin1Status()
109 | self.assertTrue(enabled)
110 |
111 | #block PIN
112 | for i in range(attemptsLeft):
113 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1_FALSE.encode("hex"))
114 |
115 | _attemptsLeft = attemptsLeft - i - 1
116 | if _attemptsLeft:
117 | types.assertSw(sw1, sw2, checkSw='GSM_ACCESS_CONDITION_NOT_FULFILLED', raiseException=True)
118 | else:
119 | types.assertSw(sw1, sw2, checkSw='GSM_UNSUCCESSFUL_USER_PIN_VERIFICATION', raiseException=True)
120 | left = self.simCtrl.pin1Status()
121 | self.assertEqual(left, _attemptsLeft)
122 |
123 | #unblock PIN
124 | enabled = self.simCtrl.pin1Enabled()
125 | attemptsLeft = self.simCtrl.pin1Status()
126 | self.assertFalse(attemptsLeft, "PIN is not blocked")
127 |
128 | attemptsLeft = self.simCtrl.pin1UnblockStatus()
129 | self.assertTrue(attemptsLeft, "SIM is permanently blocked")
130 |
131 | for i in range(attemptsLeft - 2):
132 | sw1, sw2, data = self.sendApdu("A02C000010%s%sFFFFFFFF" %(PUK_1_FALSE.encode("hex"), PIN_1.encode("hex")))
133 |
134 | _attemptsLeft = attemptsLeft - i - 1
135 | if _attemptsLeft:
136 | types.assertSw(sw1, sw2, checkSw='GSM_ACCESS_CONDITION_NOT_FULFILLED', raiseException=True)
137 | else:
138 | types.assertSw(sw1, sw2, checkSw='GSM_UNSUCCESSFUL_USER_PIN_VERIFICATION', raiseException=True)
139 | left = self.simCtrl.pin1UnblockStatus()
140 | self.assertEqual(left, _attemptsLeft)
141 |
142 | attemptsLeft = self.simCtrl.pin1UnblockStatus()
143 | self.assertGreater(attemptsLeft, 1, "Prevent permanent blocking of SIM")
144 |
145 | sw1, sw2, data = self.sendApdu("A02C000010%s%sFFFFFFFF" %(PUK_1.encode("hex"), PIN_1.encode("hex")))
146 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
147 |
148 | def test_5_read_imsi(self):
149 | enabled = self.simCtrl.pin1Enabled()
150 | attemptsLeft = self.simCtrl.pin1Status()
151 | self.assertTrue(enabled)
152 | self.assertTrue(attemptsLeft, "PUK needed")
153 |
154 | #SELECT_FILE DF_GSM
155 | sw1, sw2, data = self.sendApdu("A0A40000027F20")
156 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
157 | length = sw2
158 | #GET_RESPONSE
159 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
160 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
161 |
162 | #SELECT_FILE IMSI
163 | sw1, sw2, data = self.sendApdu("A0A40000026F07")
164 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
165 | length = sw2
166 | #GET_RESPONSE
167 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
168 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
169 |
170 | #READ BINARY
171 | length = data[3]
172 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length)
173 | if types_g.sw[(sw1<<8) + sw2] == 'GSM_ACCESS_CONDITION_NOT_FULFILLED':
174 | #VERIFY
175 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex"))
176 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
177 | #repeat READ BINARY
178 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length)
179 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
180 | imsi = hextools.decode_BCD(data)[3:]
181 | logging.info("IMSI: %s" %imsi)
182 |
183 | @unittest.skip("Might be not supported on 2G SIM")
184 | def test_6_manage_channel(self):
185 | originChannel = 0
186 | targetChannel = 2
187 |
188 | """ Select on not open channel """
189 | #SELECT_FILE MF
190 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel)
191 | types.assertSw(sw1, sw2, checkSw='LOGICAL_CHANNEL_NOT_SUPPORTED', raiseException=True)
192 |
193 | """ Open, Select and Close channel """
194 | # MANAGE CHANNEL: OPEN (chosen)
195 | sw1, sw2, data = self.sendApdu("A%01X7000%02X00" %(originChannel, targetChannel))
196 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
197 | #SELECT_FILE MF
198 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel)
199 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
200 | self.assertGreater(sw2, 2)
201 | # MANAGE CHANNEL: CLOSE
202 | sw1, sw2, data = self.sendApdu("A%01X7080%02X00" %(originChannel, targetChannel))
203 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
204 |
205 | """ Open first free channel (when targetChannel = 0) """
206 | # MANAGE CHANNEL: OPEN (first free)
207 | sw1, sw2, data = self.sendApdu("A%01X7000%02X01" %(originChannel, 0))
208 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
209 | self.assertEqual(data[0], 1)
210 |
211 | originChannel = data[0] # 1
212 | # MANAGE CHANNEL: OPEN (non-basic origin channel)
213 | sw1, sw2, data = self.sendApdu("A%01X7000%02X01" %(originChannel, 0))
214 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
215 | self.assertEqual(data[0], originChannel + 1)
216 | targetChannel = data[0] # 2
217 |
218 | """ Select IMSI file on Basic channel """
219 | #SELECT_FILE DF_GSM
220 | sw1, sw2, data = self.sendApdu("A0A40000027F20")
221 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
222 | #SELECT_FILE IMSI
223 | sw1, sw2, data = self.sendApdu("A0A40000026F07")
224 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
225 | length = sw2
226 | #GET_RESPONSE
227 | sw1, sw2, data = self.sendApdu("A0C00000%02X" %length)
228 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
229 | imsiLen = data[3]
230 | """ Select MF on Non-Basic channel """
231 | #SELECT_FILE MF
232 | sw1, sw2, data = self.sendApdu("A%01XA40000023F00" %targetChannel)
233 | types.assertSw(sw1, sw2, checkSw1='RESPONSE_DATA_AVAILABLE_2G', raiseException=True)
234 | self.assertGreater(sw2, 2)
235 | """ Read IMSI file on Basic channel """
236 | #READ BINARY
237 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %imsiLen)
238 | if types_g.sw[(sw1<<8) + sw2] == 'GSM_ACCESS_CONDITION_NOT_FULFILLED':
239 | #VERIFY
240 | sw1, sw2, data = self.sendApdu("A020000108%sFFFFFFFF" %PIN_1.encode("hex"))
241 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
242 | #repeat READ BINARY
243 | sw1, sw2, data = self.sendApdu("A0B00000%02X" %length)
244 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
245 | imsi = hextools.decode_BCD(data)[3:]
246 | logging.info("IMSI: %s" %imsi)
247 |
248 | """ Close Non-Basic channel """
249 | # MANAGE CHANNEL: CLOSE
250 | sw1, sw2, data = self.sendApdu("A%01X7080%02X00" %(originChannel, targetChannel))
251 | types.assertSw(sw1, sw2, checkSw='NO_ERROR', raiseException=True)
252 |
253 | def tearDown(self):
254 | self.simCard.reset()
255 |
256 | @classmethod
257 | def tearDownClass(cls):
258 | cls.simCard.stop()
259 |
260 | if __name__ == "__main__":
261 | logging.basicConfig(level=logging.INFO, format='%(message)s')
262 | unittest.main()
--------------------------------------------------------------------------------
/util/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kamwar/simLAB/31a4f9ad9dd40cc10fc3edb8fa8bd0be4213c5ea/util/__init__.py
--------------------------------------------------------------------------------
/util/coder.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf8
3 |
4 | import logging
5 |
6 | GSM7_BASIC = (u"@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞ\x1bÆæßÉ !\"#¤%&'()*+,-./0123456789:;<=>"
7 | u"?¡ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑÜ`¿abcdefghijklmnopqrstuvwxyzäöñüà")
8 | GSM7_EXT = (u"````````````````````^```````````````````{}`````\\````````````[~]`"
9 | u"|````````````````````````````````````€``````````````````````````")
10 |
11 | def encodeGsm7(plaintext):
12 | res = ""
13 | f = -1
14 | t = 0
15 | bytes = getBytes(plaintext)
16 | bytesLength = len(bytes)
17 | for b in bytes:
18 | f = f+1
19 | t = (f%8)+1
20 | res += getEncode(b, t, t-1, f, f+1, 8-t, bytesLength, bytes)
21 | return res
22 |
23 | def decodeGsm7(codedtext):
24 | if not codedtext:
25 | raise Exception("Empy data to decode")
26 | hexparts = chunks(codedtext, 2)
27 | number = 0
28 | bitcount = 0
29 | output = ''
30 | found_external = False
31 | for byte in hexparts:
32 | byte = int(byte, 16);
33 | # add data on to the end
34 | number = number + (byte << bitcount)
35 | # increase the counter
36 | bitcount = bitcount + 1
37 | # output the first 7 bits
38 | if number % 128 == 27:
39 | '''skip'''
40 | found_external = True
41 | else:
42 | if found_external == True:
43 | character = GSM7_EXT[number % 128]
44 | found_external = False
45 | else:
46 | character = GSM7_BASIC[number % 128]
47 | output = output + character
48 |
49 | # then throw them away
50 | number = number >> 7
51 | # every 7th letter you have an extra one in the buffer
52 | if bitcount == 7:
53 | if number % 128 == 27:
54 | '''skip'''
55 | found_external = True
56 | else:
57 | if found_external == True:
58 | character = GSM7_EXT[number % 128]
59 | found_external = False
60 | else:
61 | character = GSM7_BASIC[number % 128]
62 | output = output + character
63 |
64 | bitcount = 0
65 | number = 0
66 | return output
67 |
68 | def getEncode(currentByte, index, bitRightCount, position, nextPosition, leftShiftCount, bytesLength, bytes):
69 | if index < 8:
70 | byte = currentByte >> bitRightCount
71 | if nextPosition < bytesLength:
72 | idx2 = bytes[nextPosition]
73 | byte = byte | ((idx2) << leftShiftCount)
74 | byte = byte & 0x000000FF
75 | else:
76 | byte = byte & 0x000000FF
77 | return chr(byte).encode('hex').upper()
78 | return ''
79 |
80 | def getBytes(plaintext):
81 | if type(plaintext) != str:
82 | plaintext = str(plaintext)
83 | bytes = []
84 | for c in plaintext.decode('utf-8'):
85 | idx = GSM7_BASIC.find(c)
86 | if idx != -1:
87 | bytes.append(idx)
88 | else:
89 | idx = GSM7_EXT.find(c)
90 | if idx != -1:
91 | bytes.append(27)
92 | bytes.append(idx)
93 | return bytes
94 |
95 | def chunks(l, n):
96 | if n < 1:
97 | n = 1
98 | return [l[i:i + n] for i in range(0, len(l), n)]
99 |
--------------------------------------------------------------------------------
/util/dbus_ctrl.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # LICENSE: GPL2
3 | # (c) 2016 Janusz Kuszczynski
4 | """
5 | DBus service made as separate process, as GLib.MainLoop() is a blocking loop, interfering with gevent module.
6 | """
7 |
8 | import logging
9 | import time
10 | import multiprocessing
11 | import Queue
12 | import dbus
13 | import dbus.service
14 | from dbus.mainloop.glib import DBusGMainLoop
15 | from gi.repository import GLib
16 | import gevent
17 | from gevent import select as geventSelect
18 | import sys
19 |
20 | from sim import sim_shell
21 |
22 | def startDbusProcess(self):
23 | gevent.spawn(privateInterpreter, self) # plac.interpreter.interact is blocking, overriding
24 | taskQueue = multiprocessing.Queue()
25 | resultQueue = multiprocessing.Queue()
26 | dbusProcess = DbusProcess(taskQueue, resultQueue)
27 | dbusProcess.daemon = True
28 | dbusProcess.start()
29 | dbusReceiveTask(self, taskQueue, resultQueue) # acts as mainloop
30 | gevent.sleep(0.01) # freeze main loop for given amount of time
31 | dbusProcess.join(0.5) # try gentle
32 | dbusProcess.terminate()
33 |
34 | def getChar():
35 | if geventSelect.select([sys.stdin], [], [], 0) == ([sys.stdin], [], []): # non blocking raw_input, unix only
36 | return sys.stdin.read(1)
37 |
38 | def privateInterpreter(self):
39 | """Trivial interpreter implementation, sends command to plac interpreter"""
40 | logging.info("Starting plain interpreter")
41 | char = line = ''
42 | try:
43 | while char != '\x1b': # \x1b = escape character
44 | char = getChar()
45 | if char:
46 | line += char
47 | line += sys.stdin.readline()
48 | if '\n' in line:
49 | self.interpreter.execute([line[:-1]], verbose=True) # '[:-1]' to omit '\n' char
50 | line = ''
51 | sys.stdout.write(">")
52 | sys.stdout.flush()
53 | gevent.sleep(0.1)
54 | except KeyboardInterrupt:
55 | pass
56 |
57 | def dbusReceiveTask(self, taskQueue, resultQueue):
58 | """Loop to execute tasks from queue"""
59 | try:
60 | while True:
61 | try:
62 | task = taskQueue.get(timeout=0.1)
63 | logging.info("Received task from dbus: " + str(task))
64 | with self.interpreter:
65 | result = self.interpreter.send(task)
66 | resultQueue.put(result.str)
67 | except Queue.Empty:
68 | pass # nothing in receive task queue
69 | gevent.sleep(1)
70 | except KeyboardInterrupt:
71 | pass
72 |
73 | class DbusProcess(multiprocessing.Process):
74 | def __init__(self, taskQueue, resultQueue):
75 | multiprocessing.Process.__init__(self)
76 | self.taskQueue = taskQueue
77 | self.resultQueue = resultQueue
78 |
79 | @staticmethod
80 | def _idleQueueSync():
81 | time.sleep(0.01) # just to synchronize queue, otherwise task can be dropped
82 | return True
83 |
84 | def run(self):
85 | logging.info('D-Bus process started')
86 | GLib.threads_init() # allow threads in GLib
87 | GLib.idle_add(self._idleQueueSync)
88 |
89 | DBusGMainLoop(set_as_default=True)
90 | dbusService = SessionDBus(self.taskQueue, self.resultQueue)
91 |
92 | try:
93 | GLib.MainLoop().run()
94 | except KeyboardInterrupt:
95 | logging.debug("\nThe MainLoop will close...")
96 | GLib.MainLoop().quit()
97 | return
98 |
99 |
100 | class SessionDBus(dbus.service.Object, sim_shell.SimShell):
101 | def __init__(self, taskQueue, resultQueue):
102 | self.taskQueue = taskQueue
103 | self.resultQueue = resultQueue
104 | bus_name = dbus.service.BusName('org.sim.simlab', bus=dbus.SessionBus())
105 | dbus.service.Object.__init__(self, bus_name, '/org/sim/simlab')
106 |
107 | """
108 | Dynamically create functions, based on sim_shell.SimShell.commands. They are exported to D-Bus.
109 | """
110 | functionTemplate = "" \
111 | "def command_name(self,args=None): \n" \
112 | " self.taskQueue.put('command_name ' + args) \n" \
113 | " return self.resultQueue.get() \n" \
114 | "command_name = dbus.service.method('org.sim.simlab')(command_name) "
115 | for command in sim_shell.SimShell.commands:
116 | functionCode = functionTemplate.replace('command_name', command)
117 | # check how many parameters takes original function
118 | argumentCount = getattr(sim_shell.SimShell, command).func_code.co_argcount
119 | if argumentCount == 1:
120 | functionCode = functionCode.replace(',args=None', '')
121 | functionCode = functionCode.replace('+ args', '')
122 | exec functionCode
123 |
124 |
125 |
--------------------------------------------------------------------------------
/util/gsmtap.py:
--------------------------------------------------------------------------------
1 | # LICENSE: GPL2
2 | # (c) 2013 Tom Schouten
3 |
4 | import socket
5 | import struct
6 |
7 | # log APDUs to gsmtap
8 | # http://wiki.wireshark.org/GSMTAP
9 | # http://bb.osmocom.org/trac/wiki/GSMTAP
10 |
11 | ### to test: setup wireshark to listen on "lo"
12 | # from util import gsmtap
13 | # gsmtap.loghex("A0A40000023f00", "9000")
14 |
15 |
16 | gsmtap_hdr = [2, # GSMTAP_VERSION,
17 | 4, # nb of u32 in header
18 | 4, # GSMTAP_TYPE_SIM,
19 | 0,0,0,0,0,0,0,0,0,0,0,0,0]
20 |
21 | # gsmtap_addr = ("127.0.0.1", 4729)
22 | gsmtap_addr = ("", 4729)
23 | gsmtap_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
24 | gsmtap_sock.bind(('127.0.0.1', 0))
25 | # broadcast avoids ICMP port unreachable
26 | gsmtap_sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
27 |
28 |
29 | def bytes2str(bytes):
30 | return struct.pack('B'*len(bytes), *bytes)
31 | def hex2bytes(hex):
32 | return map(ord,hex.decode("hex"))
33 |
34 | # input = bytes
35 | def log(c_apdu, r_apdu=[]):
36 | msg = list(gsmtap_hdr) + list(c_apdu) + list(r_apdu)
37 | gsmtap_sock.sendto(bytes2str(msg), gsmtap_addr)
38 |
39 | # input = hex string
40 | def loghex(c, r=""):
41 | log(hex2bytes(c), hex2bytes(r))
42 |
--------------------------------------------------------------------------------
/util/hextools.py:
--------------------------------------------------------------------------------
1 | # LICENSE: GPL2
2 | # (c) 2013 Tom Schouten
3 |
4 | import array
5 |
6 | def bytes2hex(bytes, separator=""):
7 | hexStr = "".join(map(lambda v: "%02X%s"%(v, separator), bytes))
8 | if hexStr and hexStr[-1] == separator:
9 | #remove unwanted char at the end
10 | hexStr = hexStr[0:-1]
11 | return hexStr
12 |
13 | def hex2bytes(hex):
14 | return map(ord,hex.decode("hex"))
15 |
16 | def bytes2string(bytes):
17 | return array.array('B',bytes).tostring()
18 |
19 | def bytes(x):
20 | if type(x) == str:
21 | x = hex2bytes(x)
22 | return x
23 |
24 | def hex(x):
25 | if type(x) == str:
26 | return x
27 | if type(x) == int:
28 | return be_int_bytes(x)
29 | if type(x) == list:
30 | return bytes2hex(x)
31 | if type(x) == tuple:
32 | return bytes2hex(list(x))
33 | if type(x) == bytearray:
34 | return bytes2hex(x)
35 | raise Exception(x)
36 |
37 | def string(x):
38 | return bytes2string(bytes(x))
39 |
40 | def le_int_bytes(i):
41 | lst = []
42 | while (i != 0):
43 | lst.append(i & 0xFF)
44 | i = i >> 8
45 | return lst
46 | def be_int_bytes(i):
47 | lst = le_int_bytes(i)
48 | lst.reverse()
49 | return lst
50 |
51 | # Remove spaces
52 | def strip(str):
53 | return str.replace(" ", "")
54 |
55 |
56 | class chunks:
57 | def __init__(self, lst, nb):
58 | self.lst = lst
59 | self.nb = nb
60 | def __iter__(self):
61 | return self
62 | def next(self):
63 | if not len(self.lst):
64 | raise StopIteration
65 | rv = self.lst[0:self.nb]
66 | self.lst = self.lst[self.nb:]
67 | return rv
68 |
69 | # see iso7816_slave.h
70 | # AT91 is little endian.
71 | def le_u32(val):
72 | return [val & 0xFF,
73 | (val >> 8) & 0xFF,
74 | (val >> 16) & 0xFF,
75 | (val >> 24) & 0xFF];
76 | def u32(val):
77 | return le_u32(val)
78 |
79 | def be_u16(val):
80 | return [(val >> 8) & 0xFF, val & 0xFF]
81 |
82 |
83 | # big endian byte list to integer
84 | def le_int(lst):
85 | shift = 0
86 | acc = 0
87 | for b in lst:
88 | acc += b << shift
89 | shift += 8
90 | return acc
91 |
92 | def be_int(lst):
93 | r_lst = list(lst)
94 | r_lst.reverse()
95 | return le_int(r_lst)
96 |
97 | def all_FF(msg):
98 | for b in msg:
99 | if b != 0xFF:
100 | return False
101 | return True
102 |
103 | def pathstring(path):
104 | return "".join(map((lambda p: "/%s" % hex(p)), path))
105 |
106 |
107 | def decode_BCD(data=[]):
108 | string = ''
109 | for B in data:
110 | string += "%01X" %(B & 0x0F)
111 | hidig = B >> 4
112 | if (hidig < 10):
113 | string += str( hidig )
114 | return string
115 |
116 | def encode_BCD(data=[]):
117 | data = map(lambda x: int(x, 16), data)
118 | acc = []
119 | while len(data):
120 | head = data[0:2]
121 | data = data[2:]
122 | if (len(head) == 1):
123 | head.append(0xF)
124 | byte = head[0] + 0x10 * head[1]
125 | acc.append(byte)
126 | return acc
127 |
128 | def test():
129 | assert "00010203" == hex([0,1,2,3])
130 | assert "ABCD" == hex("ABCD")
131 | assert [0x7F, 0] == hex(0x7F00)
132 | assert 0x00010203 == be_int([0,1,2,3])
133 | assert 0x03020100 == le_int([0,1,2,3])
134 | assert "/A/B" == pathstring(["A","B"])
135 |
136 | assert [0x21,0x43] == encode_BCD("1234")
137 | assert "1234" == decode_BCD([0x21,0x43])
138 | assert [0x21,0xF3] == encode_BCD("123")
139 | assert "123" == decode_BCD([0x21,0xF3])
140 |
141 | assert [[1,2],[3,4]] == [x for x in chunks([1,2,3,4], 2)]
142 |
143 | logging.info("hextools: OK")
144 |
145 |
146 |
147 |
148 | if __name__ == '__main__':
149 | test()
150 |
--------------------------------------------------------------------------------
/util/terminal_profile.py:
--------------------------------------------------------------------------------
1 | # LICENSE: GPL2
2 | # (c) 2012 Kevin Redon
3 | # (c) 2013 Tom Schouten
4 |
5 | # how to decode the terminal profile
6 | # from ETSI TS 102 223 V11.0.0 - 5.2
7 | # Table Pythonified by Tom from from https://terminal-profile.osmocom.org/terminal_profile.rb
8 | template = [
9 | ( "Download", [
10 | (1, "Profile download"),
11 | (1, "3GPP: SMS-PP data download"),
12 | (1, "3GPP: Cell Broadcast data download"),
13 | (1, "Menu selection"),
14 | (1, "3GPP: SMS-PP data download"),
15 | (1, "Timer expiration"),
16 | (1, "3GPP: USSD string data object support in Call Control by USIM"),
17 | (1, "Call Control by NAA")
18 | ]),
19 | ( "Other", [
20 | (1, "Command result"),
21 | (1, "Call Control by NAA"),
22 | (1, "Call Control by NAA"),
23 | (1, "MO short message control support"),
24 | (1, "Call Control by NAA"),
25 | (1, "UCS2 Entry supported"),
26 | (1, "UCS2 Display supported"),
27 | (1, "Display Text")
28 | ]),
29 | ( "Proactive UICC", [
30 | (1, "DISPLAY TEXT"),
31 | (1, "GET INKEY"),
32 | (1, "GET INPUT"),
33 | (1, "MORE TIME"),
34 | (1, "PLAY TONE"),
35 | (1, "POLL INTERVAL"),
36 | (1, "POLLING OFF"),
37 | (1, "REFRESH")
38 | ]),
39 | ( "Proactive UICC", [
40 | (1, "SELECT ITEM"),
41 | (1, "3GPP: SEND SHORT MESSAGE with 3GPP-SMS-TPDU"),
42 | (1, "3GPP: SEND SS"),
43 | (1, "3GPP: SEND USSD"),
44 | (1, "SET UP CALL"),
45 | (1, "SET UP MENU"),
46 | (1, "PROVIDE LOCAL INFORMATION (MCC, MNC, LAC, Cell ID & IMEI)"),
47 | (1, "PROVIDE LOCAL INFORMATION (NMR)")
48 | ]),
49 | ( "Event driven information", [
50 | (1, "Proactive UICC: SET UP EVENT LIST"),
51 | (1, "MT call"),
52 | (1, "Call connected"),
53 | (1, "Call disconnected"),
54 | (1, "Location status"),
55 | (1, "User activity"),
56 | (1, "Idle screen available"),
57 | (1, "Card reader status")
58 | ]),
59 | ( "Event driven information extensions (for class a)", [
60 | (1, "Language selection"),
61 | (1, "Browser Termination"),
62 | (1, "Data available"),
63 | (1, "Channel status"),
64 | (1, "Access Technology Change"),
65 | (1, "Display parameters changed"),
66 | (1, "Local Connection"),
67 | (1, "Network Search Mode Change")
68 | ]),
69 | ( "Multiple card proactive commands (for class a) (Proactive UICC)", [
70 | (1, "POWER ON CARD"),
71 | (1, "POWER OFF CARD"),
72 | (1, "PERFORM CARD APDU"),
73 | (1, "GET READER STATUS (Card reader status)"),
74 | (1, "GET READER STATUS (Card reader identifier)")
75 | ]),
76 | ( "Proactive UICC", [
77 | (1, "TIMER MANAGEMENT (start, stop)"),
78 | (1, "TIMER MANAGEMENT (get current value)"),
79 | (1, "PROVIDE LOCAL INFORMATION (date, time and time zone)"),
80 | (1, "GET INKEY"),
81 | (1, "SET UP IDLE MODE TEXT"),
82 | (1, "RUN AT COMMAND"),
83 | (1, "SETUP CALL"),
84 | (1, "Call Control by NAA")
85 | ]),
86 | ( "Proactive UICC", [
87 | (1, "DISPLAY TEXT"),
88 | (1, "SEND DTMF command"),
89 | (1, "PROVIDE LOCAL INFORMATION (NMR)"),
90 | (1, "PROVIDE LOCAL INFORMATION (language)"),
91 | (1, "3GPP: PROVIDE LOCAL INFORMATION, Timing Advance"),
92 | (1, "LANGUAGE NOTIFICATION"),
93 | (1, "LAUNCH BROWSER"),
94 | (1, "PROVIDE LOCAL INFORMATION (Access Technology)")
95 | ]),
96 | ( "Soft keys support (for class d)", [
97 | (1, "Soft keys support for SELECT ITEM"),
98 | (1, "Soft keys support for SET UP MENU")
99 | ]),
100 | ( "Soft keys information", [
101 | (8, "Maximum number of soft keys available")
102 | ]),
103 | ( "Bearer Independent protocol proactive commands (for class e) (Proactive UICC)", [
104 | (1, "OPEN CHANNEL"),
105 | (1, "CLOSE CHANNEL"),
106 | (1, "RECEIVE DATA"),
107 | (1, "SEND DATA"),
108 | (1, "GET CHANNEL STATUS"),
109 | (1, "SERVICE SEARCH"),
110 | (1, "GET SERVICE INFORMATION"),
111 | (1, "DECLARE SERVICE")
112 | ]),
113 | ( "Bearer Independent protocol proactive commands (for class e)", [
114 | (1, "CSD"),
115 | (1, "GPRS"),
116 | (1, "Bluetooth"),
117 | (1, "IrDA"),
118 | (1, "RS232"),
119 | (3, "Number of channels supported by terminal")
120 | ]),
121 | ( "Screen height", [
122 | (5, "Number of characters supported down the terminal display"),
123 | (1, "No display capability"),
124 | (1, "No keypad available"),
125 | (1, "Screen Sizing Parameters supported")
126 | ]),
127 | ( "Screen width", [
128 | (7, "Number of characters supported across the terminal display"),
129 | (1, "Variable size fonts")
130 | ]),
131 | ( "Screen effects", [
132 | (1, "Display can be resized"),
133 | (1, "Text Wrapping supported"),
134 | (1, "Text Scrolling supported"),
135 | (1, "Text Attributes supported"),
136 | (1, "RFU"),
137 | (3, "Width reduction when in a menu")
138 | ]),
139 | ( "Bearer independent protocol supported transport interface/bearers (for class e)", [
140 | (1, "TCP, UICC in client mode, remote connection"),
141 | (1, "UDP, UICC in client mode, remote connection"),
142 | (1, "TCP, UICC in server mode"),
143 | (1, "TCP, UICC in client mode, local connection (i.e. class k is supported)"),
144 | (1, "UDP, UICC in client mode, local connection (i.e. class k is supported)"),
145 | (1, "Direct communication channel (i.e. class k is supported)"),
146 | (1, "3GPP: E-UTRAN"),
147 | (1, "3GPP: HSDPA")
148 | ]),
149 | ( "Proactive UICC", [
150 | (1, "DISPLAY TEXT (Variable Time out)"),
151 | (1, "GET INKEY (help is supported while waiting for immediate response or variable timeout)"),
152 | (1, "USB (Bearer Independent protocol supported bearers, class e)"),
153 | (1, "GET INKEY (Variable Timeout)"),
154 | (1, "PROVIDE LOCAL INFORMATION (ESN)"),
155 | (1, "Call control on GPRS"),
156 | (1, "PROVIDE LOCAL INFORMATION (IMEISV)"),
157 | (1, "PROVIDE LOCAL INFORMATION (Search Mode change)")
158 | ]),
159 | ( "reserved for TIA/EIA-136-C facilities", [
160 | (4, "Protocol Version support")
161 | ]),
162 | ( "reserved for TIA/EIA/IS-820-A facilities", [] ),
163 | ( "Extended Launch Browser Capability (for class c)", [
164 | (1, "WML"),
165 | (1, "XHTML"),
166 | (1, "HTML"),
167 | (1, "CHTML")
168 | ]),
169 | ( "", [
170 | (1, "3GPP: Support of UTRAN PS with extended parameters"),
171 | (1, "Proactive UICC: PROVIDE LOCAL INFORMATION (battery state), (i.e. class g is supported)"),
172 | (1, "Proactive UICC: PLAY TONE (Melody tones and Themed tones supported)"),
173 | (1, "Multi-media Calls in SET UP CALL (if class h supported)"),
174 | (1, "3GPP: Toolkit-initiated GBA"),
175 | (1, "Proactive UICC: RETRIEVE MULTIMEDIA MESSAGE (if class j is supported)"),
176 | (1, "Proactive UICC: SUBMIT MULTIMEDIA MESSAGE (if class j is supported)"),
177 | (1, "Proactive UICC: DISPLAY MULTIMEDIA MESSAGE (if class j is supported)")
178 | ]),
179 | ( "", [
180 | (1, "Proactive UICC: SET FRAMES (i.e. class i is supported)"),
181 | (1, "Proactive UICC: GET FRAMES STATUS (i.e. class i is supported)"),
182 | (1, "MMS notification download (if class j is supported)"),
183 | (1, "Alpha Identifier in REFRESH command supported by terminal"),
184 | (1, "3GPP: Geographical Location Reporting (if class n is supported)"),
185 | (1, "Proactive UICC: PROVIDE LOCAL INFORMATION (MEID)"),
186 | (1, "Proactive UICC: PROVIDE LOCAL INFORMATION (NMR(UTRAN/E-UTRAN))"),
187 | (1, "3GPP: USSD Data download and application mode")
188 | ]),
189 | ( "(for class i)", [
190 | (4, "Maximum number of frames supported (including frames created in existing frames)")
191 | ]),
192 | ( "Event driven information extensions", [
193 | (1, "Event: Browsing status"),
194 | (1, "Event: MMS Transfer status (if class j is supported)"),
195 | (1, "Event: Frame Information changed (i.e. class i is supported)"),
196 | (1, "3GPP: Event: I-WLAN Access status (if class e is supported)"),
197 | (1, "3GPP: Event Network Rejection"),
198 | (1, "Event: HCI connectivity event (i.e. class m is supported)"),
199 | (1, "3GPP: E-UTRAN support in Event Network Rejection)"),
200 | (1, "Multiple access technologies supported in Event Access Technology Change and PROVIDE LOCAL INFORMATION")
201 | ]),
202 | ( "Event driven information extensions", [
203 | (1, "Event : CSG Cell Selection (if class q is supported"),
204 | (1, "Event: Contactless state request (if class r is supported)"),
205 | (1, "Multiple access technologies supported in Event Access Technology Change and PROVIDE LOCAL INFORMATION")
206 | ]),
207 | ( "Event driven information extensions", [] ),
208 | ( "Text attributes", [
209 | (1, "Alignment left supported by Terminal"),
210 | (1, "Alignment centre supported by Terminal"),
211 | (1, "Alignment right supported by Terminal"),
212 | (1, "Font size normal supported by Terminal"),
213 | (1, "Font size large supported by Terminal"),
214 | (1, "Font size small supported by Terminal")
215 | ]),
216 | ( "Text attributes", [
217 | (1, "Style normal supported by Terminal"),
218 | (1, "Style bold supported by Terminal"),
219 | (1, "Style italic supported by Terminal"),
220 | (1, "Style underlined supported by Terminal"),
221 | (1, "Style strikethrough supported by Terminal"),
222 | (1, "Style text foreground colour supported by Terminal"),
223 | (1, "Style text background colour supported by Terminal")
224 | ]),
225 | ( "", [
226 | (1, "3GPP: I-WLAN bearer support (if class e is supported)"),
227 | (1, "3GPP: Proactive UICC: PROVIDE LOCAL INFORMATION (WSID of the current I-WLAN connection)"),
228 | (1, "TERMINAL APPLICATIONS (i.e. class k is supported)"),
229 | (1, "3GPP: Steering of Roaming REFRESH support"),
230 | (1, "Proactive UICC: ACTIVATE (i.e. class l is supported)"),
231 | (1, "3GPP: Proactive UICC: GEOGRAPHICAL LOCATION REQUEST (if class n is supported)"),
232 | (1, "Proactive UICC: PROVIDE LOCAL INFORMATION (Broadcast Network Information) (i.e. class o is supported)"),
233 | (1, "3GPP: Steering of Roaming for I-WLAN REFRESH support")
234 | ]),
235 | ( "", [
236 | (1, "Proactive UICC: Contactless State Changed (if class r is supported)"),
237 | (1, "3GPP: Support of CSG cell discovery (if class q is supported)"),
238 | (1, "Confirmation parameters supported for OPEN CHANNEL in Terminal Server Mode"),
239 | (1, "3GPP: Communication Control for IMS"),
240 | (1, "Support of CAT over the modem interface (if class s is supported)"),
241 | (1, "3GPP Support for Incoming IMS Data event (if classes e and t are supported)"),
242 | (1, "3GPP Support for IMS Registration event (if classes e and t are supported)"),
243 | (1, "Proactive UICC: Profile Container, Envelope Container, COMMAND CONTAINER and ENCAPSULATED SESSION CONTROL (if class u is supported)")
244 | ]),
245 | ( "", [
246 | (1, "3GPP: Support of IMS as a bearer for BIP (if classes e and t are supported)")
247 | ])
248 | ]
249 |
250 |
251 | def parse(bytes):
252 | for (title, fields), byte in zip(template, bytes):
253 | print title
254 | for nb_bits,name in fields:
255 | f = byte & nb_bits
256 | logging.info(" %d %s" % (f, name))
257 | byte = byte >> nb_bits
258 |
259 |
260 |
261 |
262 | def test():
263 | from util import hextools
264 | parse(hextools.bytes("FFFFFFFF1F0000DFD7030A000000000600000000")) # N1
265 |
266 | if __name__ == '__main__':
267 | test()
268 |
--------------------------------------------------------------------------------
/util/types_g.py:
--------------------------------------------------------------------------------
1 | # LICENSE: GPL2
2 | # (c) 2013 Tom Schouten
3 | # (c) 2014 Kamil Wartanowicz
4 |
5 | # Central place to store all symbolic<->numeric tag conversions.
6 |
7 | # Build bi-directional lookup with some automatic conversion
8 |
9 | # Sentinel
10 | class empty_table:
11 | def __getattr__(self, key):
12 | raise Exception("symbolic key not found: 0x%02X" % key)
13 | def __getitem__(self, key):
14 | raise Exception("numeric key not found: 0x%02X" % key)
15 |
16 | class sym_table:
17 | def __init__(self, num_to_sym, parent = empty_table()):
18 | self.parent = parent
19 | self.n2s = num_to_sym
20 | self.s2n = dict([(s,n) for n,s in num_to_sym.items()])
21 | # table.key
22 | def __getattr__(self, key):
23 | try:
24 | return self.s2n[key]
25 | except:
26 | return self.parent.__getattr__(key)
27 | # table[key]
28 | def __getitem__(self, key):
29 | try:
30 | return self.n2s[key]
31 | except:
32 | return self.parent.__getitem__(key)
33 |
34 | # ETSI TS 102 221 - 10.2.1 Status conditions returned by the UICC
35 | sw1 = sym_table({
36 | 0x61 : 'RESPONSE_DATA_AVAILABLE_3G',
37 | 0x63 : 'CODE_ATTEMPTS_LEFT',
38 | 0x67 : 'INCORRECT_PARAMETER_P3',
39 | 0x6C : 'REPEAT_COMMAND_WITH_LE',
40 | 0x6D : 'UNKNOWN_INSTRUCTION_CODE',
41 | 0x6E : 'WRONG_INSTRUCTION_CLASS',
42 | 0x6F : 'TECHNICAL_PROBLEM',
43 | 0x91 : 'NO_ERROR_PROACTIVE_DATA',
44 | 0x92 : 'NO_ERROR_DATA_TRANSFER',
45 | 0x9F : 'RESPONSE_DATA_AVAILABLE_2G',
46 | })
47 |
48 | sw = sym_table({
49 | 0x9000 : 'NO_ERROR',
50 | 0x9300 : 'TOOLKIT_BUSY',
51 | # Warnings (state of non-volatile memory unchanged).
52 | 0x6200 : 'WARNING_CARD_STATE_UNCHANGED', # (No information given)
53 | 0x6282 : 'END_OF_FILE_REACHED',
54 | 0x6283 : 'SELECTED_FILE_INVALIDATED',
55 | 0x6285 : 'SELECTED_FILE_TERMINATED',
56 | 0x62F1 : 'MORE_DATA_AVAILABLE',
57 | 0x62F2 : 'MORE_DATA_AVAILABLE_AND_PROACTIVE_COMMAND_PENDING',
58 | 0x62F3 : 'RESPONSE_DATA_AVAILABLE',
59 | # Warnings (state of non-volatile memory changed).
60 | 0x63F1 : 'MORE_DATA_EXPECTED',
61 | 0x63F2 : 'MORE_DATA_EXPECTED_AND_PROACTIVE_COMMAND_PENDING',
62 | 0x63CF : 'VERIFICATION_FAILED',
63 | # Execution errors (state of non-volatile memory unchanged).
64 | 0x6400 : 'ERROR_CARD_STATE_UNCHANGED', # (No information given)
65 | # Execution errors (state of non-volatile memory changed).
66 | 0x6500 : 'ERROR_CARD_STATE_CHANGED', # (No information given)
67 | 0x6581 : 'MEMORY_PROBLEM',
68 | # Checking errors.
69 | 0x6700 : 'WRONG_LENGTH',
70 | # Functions in CLA not supported.
71 | 0x6800 : 'FUNCTION_IN_CLA_NOT_SUPPORTED', # (No information given)
72 | 0x6881 : 'LOGICAL_CHANNEL_NOT_SUPPORTED',
73 | 0x6882 : 'SECURE_MESSAGING_NOT_SUPPORTED',
74 | # Command not allowed.
75 | 0x6900 : 'COMMAND_NOT_ALLOWED', # (No information given)
76 | 0x6981 : 'COMMAND_INCOPATIBLE_WITH_FILE_STRUCTURE',
77 | 0x6982 : 'SECURITY_STATUS_NOT_SATISFIED',
78 | 0x6983 : 'AUTHENTICATION_METHOD_BLOCKED',
79 | 0x6984 : 'REFERNCE_DATA_INVALIDATE',
80 | 0x6985 : 'CONDITIONS_OF_USE_NOT_SATISFIED',
81 | 0x6986 : 'COMMAND_NOT_ALLOWED_NO_EF_SELECTED',
82 | 0x6989 : 'COMMAND_NOT_ALLOWED_SECURE_CHANNEL_SECURITY_NOT_SATISFIED',
83 | 0x6999 : 'APPLET_SELECT_FAILED',
84 | # Wrong parameter(s) P1-P2.
85 | 0x6A80 : 'INCORRECT_PARAMETER_IN_DATA_FIELD',
86 | 0x6A81 : 'INVALID_INSTRUCTION',
87 | 0x6A82 : 'FILE_NOT_FOUND',
88 | 0x6A83 : 'INVALID_DATA_ADDRESS',
89 | 0x6A84 : 'NOT_ENOUGH_MEMORY_SPACE',
90 | 0x6A86 : 'INCORRECT_PARAMETERS_P1_P2', # for SFI mode (out of range)
91 | 0x6A87 : 'INCORRECT_PARAMETER_P3', # Lc
92 | 0x6A88 : 'REFERENCE_DATA_NOT_FOUND',
93 | 0x6A89 : 'FILE_ID_ALREADY_EXISTS',
94 | 0x6A8A : 'DF_NAME_ALREADY_EXISTS',
95 | # Checking errors.
96 | 0x6B00 : 'WRONG_PARAMETERS_P1_P2',
97 | 0x6D00 : 'INVALID_INSTRUCTION_OR_NOT_SUPPORTED',
98 | 0x6E00 : 'CLASS_NOT_SUPPORTED',
99 | 0x6F00 : 'TECHNICAL_PROBLEM',
100 | # 2G/GSM codes.
101 | 0x9400 : 'GSM_COMMAND_NOT_ALLOWED_NO_EF_SELECTED',
102 | 0x9402 : 'GSM_INVALID_DATA_ADDRESS', #SC
103 | 0x9404 : 'GSM_FILE_NOT_FOUND',
104 | 0x9408 : 'GSM_COMMAND_INCOPATIBLE_WITH_FILE_STRUCTURE',
105 | 0x9802 : 'GSM_CHV_NOT_ACTIVE',
106 | 0x9802 : 'GSM_CHV_IS_INVALIDATED',
107 | 0x9804 : 'GSM_ACCESS_CONDITION_NOT_FULFILLED',
108 | 0x9808 : 'GSM_CHV_ALREADY_VALIDATED',
109 | 0x9810 : 'GSM_SELECTED_FILE_INVALIDATED',
110 | 0x9840 : 'GSM_UNSUCCESSFUL_USER_PIN_VERIFICATION',
111 | # Applications errors.
112 | 0x9850 : 'INCREASE_CANNOT_BE_PERFORMED_MAX_REACHED',
113 | 0x9862 : 'AUTHENTICATION_ERROR_APPLICATION_SPECIFIC',
114 | 0x9863 : 'SECURITY_SESSION_OR_ASSOCIATION_EXPIRED',
115 | })
116 |
117 | iso7816 = sym_table({
118 | 0x04 : 'DEACTIVATE_FILE',
119 | 0x0E : 'ERASE_BINARY',
120 | 0x10 : 'TERMINAL_PROFILE',
121 | 0x12 : 'FETCH',
122 | 0x14 : 'TERMINAL_RESPONSE',
123 | 0x20 : 'VERIFY_PIN',
124 | 0x24 : 'CHANGE_PIN',
125 | 0x26 : 'DISABLE_PIN',
126 | 0x28 : 'ENABLE_PIN',
127 | 0x2C : 'UNBLOCK_PIN',
128 | 0x32 : 'INCREASE',
129 | 0x44 : 'ACTIVATE_FILE',
130 | 0x70 : 'MANAGE_CHANNEL',
131 | 0x73 : 'MANAGE_SECURE_CHANNEL',
132 | 0x75 : 'TRANSACT_DATA',
133 | 0x82 : 'EXTERNAL_AUTHENTICATE',
134 | 0x84 : 'GET_CHALLENGE',
135 | 0x88 : 'INTERNAL_AUTHENTICATE',
136 | 0x89 : 'INTERNAL_AUTHENTICATE2',
137 | 0xA2 : 'SEARCH_RECORD',
138 | 0xA4 : 'SELECT_FILE',
139 | 0xAA : 'TERMINAL_CAPABILITY',
140 | 0xB0 : 'READ_BINARY',
141 | 0xB2 : 'READ_RECORD',
142 | 0xC0 : 'GET_RESPONSE',
143 | 0xC2 : 'ENVELOPE',
144 | 0xCB : 'RETRIEVE_DATA',
145 | 0xCA : 'GET_DATA',
146 | 0xD0 : 'WRITE_BINARY',
147 | 0xD2 : 'WRITE_RECORD',
148 | 0xD4 : 'RESIZE_FILE',
149 | 0xD6 : 'UPDATE_BINARY',
150 | 0xDA : 'PUT_DATA',
151 | 0xDB : 'SET_DATA',
152 | 0xDC : 'UPDATE_RECORD',
153 | 0xE0 : 'CREATE_FILE',
154 | 0xE2 : 'APPEND_RECORD',
155 | 0xE4 : 'DELETE_FILE',
156 | 0xF2 : 'STATUS',
157 | })
158 |
159 | # ETSI TS 102 223 - 9.4
160 | proactive_command = sym_table({
161 | 0x03 : 'POLL_INTERVAL',
162 | 0x13 : 'SEND_SHORT_MESSAGE',
163 | 0x16 : 'GEOGRAPHICAL_LOCATION_REQUEST',
164 | })
165 |
166 | # ETSI TS 101 220 - 7.2 Assigned TLV tag values
167 | # Card application toolkit templates
168 | cat = sym_table({
169 | 0xD0 : 'PROACTIVE_COMMAND',
170 | 0xD1 : 'SMS_PP_DOWNLOAD',
171 | })
172 |
173 | # ETSI TS 101 220 - 7.2 Assigned TLV tag values
174 | # Card application toolkit data objects
175 | cat_data = sym_table({
176 | 0x81 : 'COMMAND_DETAILS',
177 | 0x82 : 'DEVICE_IDENTITY',
178 | 0x0B : 'SMS_PDU',
179 | })
180 |
181 | # ETSI TS 101 220 - 7.2 Assigned TLV tag values
182 | # Proprietary information ('A5')
183 | properietaryTag = sym_table({
184 | 0x80 : 'UICC_CHARACTERISTICS',
185 | 0x81 : 'APP_POWER_CONSUMPTION',
186 | 0x82 : 'MIN_APP_CLOCK_FREQ',
187 | 0x83 : 'AMOUNT_OF_AVAIL_MEMORY',
188 | 0x84 : 'FILE_DETAILS',
189 | 0x85 : 'RESERVED_FILE_SIZE',
190 | 0x86 : 'MAXIMUM_FILE_SIZE',
191 | 0x87 : 'SUPPORTED_SYSTEM_COMMANDS',
192 | 0x88 : 'SPECIFIC_UICC_ENV_CONDITIONS'
193 | })
194 |
195 | # ETSI TS 101 220 - 7.2 Assigned TLV tag values
196 | # PIN Status data objects ('C6')
197 | pinStatusTag = sym_table({
198 | 0x83 : 'KEY_REFERENCE',
199 | 0x90 : 'PIN_STATUS',
200 | 0x95 : 'USAGE_QUALIFIER',
201 | })
202 |
203 | #ts_102221v080200p 11.1.1.3 Response Data
204 | selectTag = sym_table({
205 | 0x62 : "FCP",
206 | 0x80 : "FILE_SIZE",
207 | 0x81 : "TOTAL_FILE_SIZE",
208 | 0x82 : "FILE_DESCRIPTOR",
209 | 0x83 : "FILE_IDENTIFIER",
210 | 0x84 : "DF_NAME",
211 | 0x88 : "SHORT_FILE_IDENTIFIER",
212 | 0x8A : "LIFE_CYCLE_STATUS",
213 | 0x8B : "SECURITY_ATRIBUTES_COMPACT",
214 | 0x8C : "SECURITY_ATRIBUTES_REF_EXPANDED",
215 | 0xAB : "SECURITY_ATRIBUTES_EXPANDED",
216 | 0xA5 : "PROPRIETARY_INF",
217 | 0xC6 : "PIN_STATUS_TEMPLATE",
218 | })
219 |
220 | #ts_102221v080200p Table 11.5: File descriptor byte
221 | fileDecriptorMask = sym_table({
222 | 0b01000000 : "FILE_ACCESSIBILITY",
223 | 0b00111000 : "FILE_TYPE",
224 | 0b00000111 : "EF_STRUCTURE"
225 | })
226 |
227 | #ts_102221v080200p Table 11.5: File descriptor byte
228 | fileDescriptor = sym_table({
229 | 0b00000000 : "NOT_SHAREABLE",
230 | 0b01000000 : "SHAREABLE",
231 | 0b00000000 : "WORKING_EF",
232 | 0b00010000 : "INTERNAL_EF",
233 | 0b00111000 : "DF_OR_ADF",
234 | 0b00000000 : "NO_INFORMATION_GIVEN", #b4-b6 not all set to 1
235 | 0b00000001 : "TRANSPARENT_STRUCTURE",
236 | 0b00000010 : "LINEAR_FIXED_STRUCTURE",
237 | 0b00000110 : "CYCLIC_STRUCTURE",
238 | #refactor, cannot be the same value as TRANSPARENT_STRUCTURE
239 | #0b00000001 : "BER_TLV_STRUCTURE", #b4-b6 all set to 1
240 | })
241 |
242 | # 3G specific
243 | fileSearchType = sym_table({
244 | 0b00000100 : "FORWARD_SEARCH_FROM_P1",
245 | 0b00000101 : "BACKWARD_SEARCH_FROM_P1",
246 | 0b00000110 : "ENHENCED_SEARCH",
247 | 0b00000111 : "PROPERIETARY_SEARCH"
248 | })
249 |
250 | fileSearchIndication = sym_table({
251 | 0b00000100 : "FORWARD_SEARCH_FROM_P1",
252 | 0b00000101 : "BACKWARD_SEARCH_FROM_P1",
253 | 0b00000110 : "FORWARD_SEARCH_FROM_NEXT_RECORD",
254 | 0b00000111 : "BACKWARD_SEARCH_FROM_PREVIOUS_RECORD"
255 | })
256 |
257 | searchStartMode = sym_table({
258 | 0b00000000 : "START_FROM_OFFSET",
259 | 0b00001000 : "START_FROM_VALUE",
260 | })
261 |
262 | # 2G specific
263 | fileSeekType = sym_table({
264 | 0x00 : "TYPE_1",
265 | 0x10 : "TYPE_2"
266 | })
267 |
268 | # with x='0' specifies type 1 and x='1' specifies type 2 of the SEEK command
269 | fileSeekMode = sym_table({
270 | 0x00 : "FROM_THE_BEGINNING_FORWARD",
271 | 0x01 : "FROM_THE_END_BACKWARD",
272 | 0x02 : "FROM_THE_NEXT_LOCATION_FORWARD",
273 | 0x03 : "FROM_THE_PREVIOUS_LOCATION_BACKWARD",
274 | })
275 |
276 | verifyChvP2_3g = sym_table({
277 | 0x01 : "chv1", # PIN1
278 | 0x81 : "chv2", # PIN2
279 | 0x0A : "adm1",
280 | 0x0B : "adm2",
281 | 0x0C : "adm3",
282 | 0x0D : "adm4",
283 | })
284 |
285 | # 2G specific
286 | verifyChvP2_2g = sym_table({
287 | 0x01 : "chv1",
288 | 0x02 : "chv2",
289 | 0x0B : "adm1",
290 | })
291 |
292 | # 2G specific
293 | verifyChvUnblockP2 = sym_table({
294 | 0x00 : "chv1",
295 | 0x02 : "chv2"
296 | })
297 |
298 | readRecordSelect = sym_table({
299 | 0b00000000 : "CURRENT_EF",
300 | 0b11111000 : "SFI",
301 | })
302 |
303 | readRecordMode = sym_table({
304 | 0b00000010 : "NEXT_RECORD",
305 | 0b00000011 : "PREVIOUS_RECORD",
306 | 0b00000100 : "ABSOLUTE_OR_CURRENT",
307 | })
308 |
309 | binaryCmdP1 = sym_table({
310 | 0b10000000 : "SFI_MODE",
311 | 0b00000111 : "SFI_MASK",
312 | })
313 |
314 | adfName = sym_table({
315 | 0x02 : 'ADF_USIM',
316 | 0x04 : 'ADF_ISIM'
317 | })
318 |
--------------------------------------------------------------------------------