blob: d876dca2bb07ffa5ac9af2578e40996225e19e24 [file] [log] [blame]
Oliver Smith05b13322020-02-24 14:18:20 +01001/* Copyright 2020 sysmocom s.f.m.c. GmbH
2 * SPDX-License-Identifier: Apache-2.0 */
Oliver Smith4e5e5162020-02-21 08:47:36 +01003package org.osmocom.IMSIPseudo;
Neels Hofmeyrd20f93a2020-02-24 22:42:22 +01004import org.osmocom.IMSIPseudo.MobileIdentity;
Oliver Smith4e5e5162020-02-21 08:47:36 +01005
Oliver Smith4eee13d2020-02-24 11:28:39 +01006import sim.access.*;
7import sim.toolkit.*;
8import javacard.framework.*;
Oliver Smith4e5e5162020-02-21 08:47:36 +01009
10public class IMSIPseudo extends Applet implements ToolkitInterface, ToolkitConstants {
11 // DON'T DECLARE USELESS INSTANCE VARIABLES! They get saved to the EEPROM,
12 // which has a limited number of write cycles.
Oliver Smith4e5e5162020-02-21 08:47:36 +010013
Oliver Smithca866fe2020-02-24 09:56:30 +010014 private byte STKServicesMenuId;
Oliver Smith2259cb92020-02-24 11:36:31 +010015 private SIMView gsmFile;
Neels Hofmeyrcf04db02020-02-25 03:23:03 +010016 static byte[] LUCounter = { '0', 'x', ' ', 'L', 'U' };
Oliver Smithca866fe2020-02-24 09:56:30 +010017
18 /* Main menu */
Neels Hofmeyrcf04db02020-02-25 03:23:03 +010019 private static final byte[] title = { 'I', 'M', 'S', 'I', ' ', 'P', 's', 'e', 'u', 'd', 'o', 'n', 'y', 'm',
Oliver Smith2dcbfab2020-02-21 15:40:21 +010020 'i', 'z', 'a', 't', 'i', 'o', 'n'};
Neels Hofmeyrcf04db02020-02-25 03:23:03 +010021 private static final byte[] showLU = {'S', 'h', 'o', 'w', ' ', 'L', 'U', ' ', 'c', 'o', 'u', 'n', 't', 'e', 'r'};
Neels Hofmeyr483f5a42020-02-25 03:06:12 +010022 private static final byte[] changeIMSI = {'C', 'h', 'a', 'n', 'g', 'e', ' ', 'I', 'M', 'S', 'I'};
23 private static final byte[] invalidIMSI = {'I', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'I', 'M', 'S', 'I'};
24 private static final byte[] noChange = {'N', 'o', ' ', 'c', 'h', 'a', 'n', 'g', 'e'};
25 private static final byte[] changed = {'I', 'M', 'S', 'I', ' ', 'c', 'h', 'a', 'n', 'g', 'e', 'd', '!'};
26 private static final byte error[] = {'E', 'R', 'R', 'O', 'R' };
27 private final Object[] itemListMain = {title, showLU, changeIMSI};
Oliver Smith4e5e5162020-02-21 08:47:36 +010028
29 private IMSIPseudo() {
Oliver Smith2259cb92020-02-24 11:36:31 +010030 gsmFile = SIMSystem.getTheSIMView();
31
Oliver Smithca866fe2020-02-24 09:56:30 +010032 /* Register menu and trigger on location updates */
Oliver Smith4e5e5162020-02-21 08:47:36 +010033 ToolkitRegistry reg = ToolkitRegistry.getEntry();
Oliver Smithca866fe2020-02-24 09:56:30 +010034 STKServicesMenuId = reg.initMenuEntry(title, (short)0, (short)title.length, PRO_CMD_SELECT_ITEM, false,
35 (byte)0, (short)0);
Oliver Smithe28705a2020-02-21 10:06:14 +010036 reg.setEvent(EVENT_EVENT_DOWNLOAD_LOCATION_STATUS);
Oliver Smith4e5e5162020-02-21 08:47:36 +010037 }
38
Oliver Smith4e5e5162020-02-21 08:47:36 +010039 public static void install(byte[] bArray, short bOffset, byte bLength) {
40 IMSIPseudo applet = new IMSIPseudo();
41 applet.register();
42 }
43
Oliver Smith4e5e5162020-02-21 08:47:36 +010044 public void process(APDU arg0) throws ISOException {
Oliver Smith4e5e5162020-02-21 08:47:36 +010045 if (selectingApplet())
46 return;
47 }
48
Oliver Smith4e5e5162020-02-21 08:47:36 +010049 public void processToolkit(byte event) throws ToolkitException {
50 EnvelopeHandler envHdlr = EnvelopeHandler.getTheHandler();
51
52 if (event == EVENT_MENU_SELECTION) {
53 byte selectedItemId = envHdlr.getItemIdentifier();
54
Oliver Smithca866fe2020-02-24 09:56:30 +010055 if (selectedItemId == STKServicesMenuId) {
Neels Hofmeyr583bfec2020-02-25 03:10:38 +010056 showMenu(itemListMain);
Oliver Smithca866fe2020-02-24 09:56:30 +010057 handleMenuResponseMain();
Oliver Smith4e5e5162020-02-21 08:47:36 +010058 }
59 }
Oliver Smithe28705a2020-02-21 10:06:14 +010060
61 if (event == EVENT_EVENT_DOWNLOAD_LOCATION_STATUS) {
Oliver Smith1e5cc462020-02-21 15:39:14 +010062 LUCounter[0]++;
Oliver Smith234ab542020-02-24 08:25:43 +010063 showMsg(LUCounter);
Oliver Smithe28705a2020-02-21 10:06:14 +010064 }
Oliver Smith4e5e5162020-02-21 08:47:36 +010065 }
66
Neels Hofmeyr583bfec2020-02-25 03:10:38 +010067 private void showMenu(Object[] itemList) {
Oliver Smithca866fe2020-02-24 09:56:30 +010068 ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
69 proHdlr.init((byte) PRO_CMD_SELECT_ITEM,(byte)0,DEV_ID_ME);
70
Neels Hofmeyr583bfec2020-02-25 03:10:38 +010071 for (byte i=(byte)0; i < itemList.length; i++) {
Oliver Smithca866fe2020-02-24 09:56:30 +010072 if (i == 0) {
73 /* Title */
74 proHdlr.appendTLV((byte)(TAG_ALPHA_IDENTIFIER | TAG_SET_CR), (byte[])itemList[i],
75 (short)0, (short)((byte[])itemList[i]).length);
76
77 } else {
78 /* Menu entry */
79 proHdlr.appendTLV((byte)(TAG_ITEM | TAG_SET_CR), (byte)i, (byte[])itemList[i], (short)0,
80 (short)((byte[])itemList[i]).length);
81 }
82 }
83 proHdlr.send();
84 }
85
Oliver Smithcef081c2020-02-24 10:02:14 +010086 private void showMsg(byte[] msg) {
87 ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
88 proHdlr.initDisplayText((byte)0, DCS_8_BIT_DATA, msg, (short)0, (short)(msg.length));
89 proHdlr.send();
Oliver Smithcef081c2020-02-24 10:02:14 +010090 }
91
Neels Hofmeyrba7a6f22020-02-24 21:26:37 +010092 private byte[] getResponse()
93 {
94 ProactiveResponseHandler rspHdlr = ProactiveResponseHandler.getTheHandler();
95 byte[] resp = new byte[rspHdlr.getTextStringLength()];
96 rspHdlr.copyTextString(resp, (short)0);
97 return resp;
98 }
99
Neels Hofmeyr9a3428e2020-02-25 03:21:12 +0100100 /*
101 This was used to find out that the first byte of a text field seems to be 4.
102 private byte[] getResponseDBG()
103 {
104 ProactiveResponseHandler rspHdlr;
105 byte resp[];
106 byte strlen = -1;
107 rspHdlr = ProactiveResponseHandler.getTheHandler();
108
109 for (byte occurence = 1; occurence <= 3; occurence++) {
110 short len;
111 try {
112 if (rspHdlr.findTLV(TAG_TEXT_STRING, (byte)occurence) != TLV_NOT_FOUND) {
113 if ((len = rspHdlr.getValueLength()) > 1) {
114 len = 3;
115 resp = new byte[len];
116 rspHdlr.copyValue((short)0, resp, (short)0, (short)(len));
117 showMsg(resp);
118 showMsgAndWaitKey(Bytes.hexdump(resp));
119 return resp;
120 }
121 }
122 } catch (Exception e) {
123 showError((short)(30 + occurence));
124 return null;
125 }
126 }
127 showError((short)(39));
128 return null;
129 }
130 */
131
Neels Hofmeyrba7a6f22020-02-24 21:26:37 +0100132 private byte[] showMsgAndWaitKey(byte[] msg) {
Neels Hofmeyrcfb476d2020-02-24 19:00:03 +0100133 ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
134 proHdlr.initGetInkey((byte)0, DCS_8_BIT_DATA, msg, (short)0, (short)(msg.length));
135 proHdlr.send();
Neels Hofmeyrba7a6f22020-02-24 21:26:37 +0100136
137 return getResponse();
138 }
139
Neels Hofmeyr9a3428e2020-02-25 03:21:12 +0100140 private byte[] prompt(byte[] msg, byte[] prefillVal, short minLen, short maxLen) {
Neels Hofmeyrba7a6f22020-02-24 21:26:37 +0100141 /* if maxLen < 1, the applet crashes */
142 if (maxLen < 1)
143 maxLen = 1;
144
145 ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
146 proHdlr.initGetInput((byte)0, DCS_8_BIT_DATA, msg, (short)0, (short)(msg.length), minLen, maxLen);
Neels Hofmeyr9a3428e2020-02-25 03:21:12 +0100147 if (prefillVal != null && prefillVal.length > 0) {
148 /* appendTLV() expects the first byte to be some header before the actual text.
149 * At first I thought it was the value's length, but turned out to only work for lengths under 8...
150 * In the end I reversed the value 4 from the first byte read by rspHdlr.copyValue() for
151 * TAG_TEXT_STRING fields. As long as we write 4 into the first byte, things just work out,
152 * apparently.
153 * Fucking well could have said so in the API docs, too; oh the brain damage, oh the hours wasted.
154 * This is the appendTLV() variant that writes one byte ahead of writing an array: */
155 proHdlr.appendTLV((byte)(TAG_DEFAULT_TEXT), (byte)4, prefillVal, (short)0,
156 (short)(prefillVal.length));
157 }
Neels Hofmeyrba7a6f22020-02-24 21:26:37 +0100158 proHdlr.send();
159
160 return getResponse();
Neels Hofmeyrcfb476d2020-02-24 19:00:03 +0100161 }
162
Oliver Smithd7f18922020-02-24 12:24:38 +0100163 private void showError(short code) {
Neels Hofmeyrcf04db02020-02-25 03:23:03 +0100164 byte[] msg = {'E', '?', '?'};
Oliver Smithd7f18922020-02-24 12:24:38 +0100165 msg[1] = (byte)('0' + code / 10);
166 msg[2] = (byte)('0' + code % 10);
167 showMsg(msg);
168 }
169
Oliver Smithca866fe2020-02-24 09:56:30 +0100170 private void handleMenuResponseMain() {
171 ProactiveResponseHandler rspHdlr = ProactiveResponseHandler.getTheHandler();
172
173 switch (rspHdlr.getItemIdentifier()) {
Neels Hofmeyrc8e96412020-02-24 21:29:46 +0100174 case 1: /* Show LU counter */
175 showMsg(LUCounter);
176 break;
Neels Hofmeyr483f5a42020-02-25 03:06:12 +0100177 case 2: /* Change IMSI */
178 byte prevIMSI_mi[] = readIMSI();
179 byte prevIMSI_str[] = MobileIdentity.mi2str(prevIMSI_mi);
180 promptIMSI(prevIMSI_str);
Neels Hofmeyrc8e96412020-02-24 21:29:46 +0100181 break;
Oliver Smithca866fe2020-02-24 09:56:30 +0100182 }
183 }
184
Neels Hofmeyr483f5a42020-02-25 03:06:12 +0100185 private void promptIMSI(byte prevIMSI_str[])
Neels Hofmeyr0866f3a2020-02-24 21:30:42 +0100186 {
Neels Hofmeyr483f5a42020-02-25 03:06:12 +0100187 byte newIMSI_str[] = prevIMSI_str;
188
189 try {
190 newIMSI_str = prompt(changeIMSI, newIMSI_str, (short)0, (short)15);
191 } catch (Exception e) {
192 showError((short)40);
193 return;
194 }
195
196 if (newIMSI_str.length < 6 || newIMSI_str.length > 15
197 || !Bytes.isDigit(newIMSI_str)) {
198 showMsg(invalidIMSI);
199 return;
200 }
201
202 if (Bytes.equals(newIMSI_str, prevIMSI_str)) {
203 showMsg(noChange);
204 return;
205 }
206
Neels Hofmeyrd20f93a2020-02-24 22:42:22 +0100207 byte mi[];
208 try {
Neels Hofmeyr483f5a42020-02-25 03:06:12 +0100209 /* The IMSI file should be 9 bytes long, even if the IMSI is shorter */
210 mi = MobileIdentity.str2mi(newIMSI_str, MobileIdentity.MI_IMSI, (byte)9);
Neels Hofmeyr41b6f542020-02-24 23:00:30 +0100211 writeIMSI(mi);
Neels Hofmeyr483f5a42020-02-25 03:06:12 +0100212 showMsg(changed);
Neels Hofmeyr4ac43a22020-02-26 02:02:53 +0100213 refreshIMSI();
Neels Hofmeyrd20f93a2020-02-24 22:42:22 +0100214 } catch (Exception e) {
Neels Hofmeyr483f5a42020-02-25 03:06:12 +0100215 showError((short)42);
Neels Hofmeyrd20f93a2020-02-24 22:42:22 +0100216 }
Oliver Smithca866fe2020-02-24 09:56:30 +0100217 }
Neels Hofmeyrc24fdd12020-02-24 21:31:01 +0100218
219 private byte[] readIMSI()
220 {
221 gsmFile.select((short) SIMView.FID_DF_GSM);
222 gsmFile.select((short) SIMView.FID_EF_IMSI);
223 byte[] IMSI = new byte[9];
224 gsmFile.readBinary((short)0, IMSI, (short)0, (short)9);
225 return IMSI;
226 }
227
Neels Hofmeyr26256942020-02-25 03:23:53 +0100228 private void writeIMSI(byte mi[]) throws Exception
Neels Hofmeyrc24fdd12020-02-24 21:31:01 +0100229 {
Neels Hofmeyr26256942020-02-25 03:23:53 +0100230 if (mi.length != 9)
231 throw new Exception();
Neels Hofmeyrc24fdd12020-02-24 21:31:01 +0100232 gsmFile.select((short) SIMView.FID_DF_GSM);
233 gsmFile.select((short) SIMView.FID_EF_IMSI);
234 gsmFile.updateBinary((short)0, mi, (short)0, (short)mi.length);
235 }
Neels Hofmeyr4ac43a22020-02-26 02:02:53 +0100236
237 /*
238 * - command qualifiers for REFRESH,
239 * ETSI TS 101 267 / 3GPP TS 11.14 chapter 12.6 "Command details":
240 * '00' =SIM Initialization and Full File Change Notification;
241 * '01' = File Change Notification;
242 * '02' = SIM Initialization and File Change Notification;
243 * '03' = SIM Initialization;
244 * '04' = SIM Reset;
245 * '05' to 'FF' = reserved values.
246 */
247 public static final byte SIM_REFRESH_SIM_INIT_FULL_FILE_CHANGE = 0x00;
248 public static final byte SIM_REFRESH_FILE_CHANGE = 0x01;
249 public static final byte SIM_REFRESH_SIM_INIT_FILE_CHANGE = 0x02;
250 public static final byte SIM_REFRESH_SIM_INIT = 0x03;
251 public static final byte SIM_REFRESH_SIM_RESET = 0x04;
252
253 /* Run the Proactive SIM REFRESH command for the FID_EF_IMSI. */
254 private void refreshIMSI()
255 {
256 /* See ETSI TS 101 267 / 3GPP TS 11.14 section 6.4.7.1 "EF IMSI changing procedure":
257 * Valid qualifiers are SIM_REFRESH_SIM_INIT_FILE_CHANGE and SIM_REFRESH_SIM_INIT_FULL_FILE_CHANGE.
258 */
259 ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
260 proHdlr.init((byte)PRO_CMD_REFRESH, SIM_REFRESH_SIM_INIT_FULL_FILE_CHANGE, DEV_ID_ME);
261 proHdlr.send();
262 }
Oliver Smith4e5e5162020-02-21 08:47:36 +0100263}