aboutsummaryrefslogtreecommitdiffstats
path: root/imsi-change/src/org/osmocom/IMSIChange/IMSIChange.java
blob: 61355964c7fd2e1071006740b1086c8e2cdc17fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
/* Copyright 2020 sysmocom s.f.m.c. GmbH
 * SPDX-License-Identifier: Apache-2.0 */
package org.osmocom.IMSIChange;
import org.osmocom.IMSIChange.MobileIdentity;

import sim.access.*;
import sim.toolkit.*;
import javacard.framework.*;

public class IMSIChange extends Applet implements ToolkitInterface, ToolkitConstants {
	// DON'T DECLARE USELESS INSTANCE VARIABLES! They get saved to the EEPROM,
	// which has a limited number of write cycles.

	private byte STKServicesMenuId;
	private SIMView gsmFile;

	/* Main menu */
	private static final byte[] changeIMSI = {'C', 'h', 'a', 'n', 'g', 'e', ' ', 'I', 'M', 'S', 'I'};
	private static final byte[] invalidIMSI = {'I', 'n', 'v', 'a', 'l', 'i', 'd', ' ', 'I', 'M', 'S', 'I'};
	private static final byte[] noChange = {'N', 'o', ' ', 'c', 'h', 'a', 'n', 'g', 'e'};
	private static final byte[] changed = {'I', 'M', 'S', 'I', ' ', 'c', 'h', 'a', 'n', 'g', 'e', 'd', '!'};

	private IMSIChange() {
		gsmFile = SIMSystem.getTheSIMView();

		ToolkitRegistry reg = ToolkitRegistry.getEntry();
		STKServicesMenuId = reg.initMenuEntry(changeIMSI, (short)0, (short)changeIMSI.length,
						      PRO_CMD_SELECT_ITEM, false, (byte)0, (short)0);
	}

	public static void install(byte[] bArray, short bOffset, byte bLength) {
		IMSIChange applet = new IMSIChange();
		applet.register();
	}

	public void process(APDU arg0) throws ISOException {
		if (selectingApplet())
			return;
	}

	public void processToolkit(byte event) throws ToolkitException {
		EnvelopeHandler envHdlr = EnvelopeHandler.getTheHandler();

		if (event == EVENT_MENU_SELECTION) {
			byte selectedItemId = envHdlr.getItemIdentifier();

			if (selectedItemId == STKServicesMenuId) {
				byte prevIMSI_mi[] = readIMSI();
				byte prevIMSI_str[] = MobileIdentity.mi2str(prevIMSI_mi);
				promptIMSI(prevIMSI_str);
			}
		}
	}

	private void showMsg(byte[] msg) {
		ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
		proHdlr.initDisplayText((byte)0, DCS_8_BIT_DATA, msg, (short)0, (short)(msg.length));
		proHdlr.send();
	}

	private byte[] getResponse()
	{
		ProactiveResponseHandler rspHdlr = ProactiveResponseHandler.getTheHandler();
		byte[] resp = new byte[rspHdlr.getTextStringLength()];
		rspHdlr.copyTextString(resp, (short)0);
		return resp;
	}

	private byte[] prompt(byte[] msg, byte[] prefillVal, short minLen, short maxLen) {
		/* if maxLen < 1, the applet crashes */
		if (maxLen < 1)
			maxLen = 1;

		ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
		proHdlr.initGetInput((byte)0, DCS_8_BIT_DATA, msg, (short)0, (short)(msg.length), minLen, maxLen);
		if (prefillVal != null && prefillVal.length > 0) {
			/* appendTLV() expects the first byte to be some header before the actual text.
			 * At first I thought it was the value's length, but turned out to only work for lengths under 8...
			 * In the end I reversed the value 4 from the first byte read by rspHdlr.copyValue() for
			 * TAG_TEXT_STRING fields. As long as we write 4 into the first byte, things just work out,
			 * apparently.
			 * This is the appendTLV() variant that writes one byte ahead of writing an array: */
			proHdlr.appendTLV((byte)(TAG_DEFAULT_TEXT), (byte)4, prefillVal, (short)0,
					  (short)(prefillVal.length));
		}
		proHdlr.send();

		return getResponse();
	}

	private void showError(short code) {
		byte[] msg = {'E', '?', '?'};
		msg[1] = (byte)('0' + code / 10);
		msg[2] = (byte)('0' + code % 10);
		showMsg(msg);
	}

	private void promptIMSI(byte prevIMSI_str[])
	{
		byte newIMSI_str[] = prevIMSI_str;

		try {
			newIMSI_str = prompt(changeIMSI, newIMSI_str, (short)0, (short)15);
		} catch (Exception e) {
			showError((short)40);
			return;
		}

		if (newIMSI_str.length < 6 || newIMSI_str.length > 15
		    || !Bytes.isDigit(newIMSI_str)) {
			showMsg(invalidIMSI);
			return;
		}

		if (Bytes.equals(newIMSI_str, prevIMSI_str)) {
			showMsg(noChange);
			return;
		}

		byte mi[];
		try {
			/* The IMSI file should be 9 bytes long, even if the IMSI is shorter */
			mi = MobileIdentity.str2mi(newIMSI_str, MobileIdentity.MI_IMSI, (byte)9);
			writeIMSI(mi);
			showMsg(changed);
			refreshIMSI();
		} catch (Exception e) {
			showError((short)42);
		}
	}

	private byte[] readIMSI()
	{
		gsmFile.select((short) SIMView.FID_DF_GSM);
		gsmFile.select((short) SIMView.FID_EF_IMSI);
		byte[] IMSI = new byte[9];
		gsmFile.readBinary((short)0, IMSI, (short)0, (short)9);
		return IMSI;
	}

	private void writeIMSI(byte mi[]) throws Exception
	{
		if (mi.length != 9)
			throw new Exception();
		gsmFile.select((short) SIMView.FID_DF_GSM);
		gsmFile.select((short) SIMView.FID_EF_IMSI);
		gsmFile.updateBinary((short)0, mi, (short)0, (short)mi.length);
	}

	/*
	 * - command qualifiers for REFRESH,
	 *   ETSI TS 101 267 / 3GPP TS 11.14 chapter 12.6 "Command details":
	 *   '00' =SIM Initialization and Full File Change Notification;
	 *   '01' = File Change Notification;
	 *   '02' = SIM Initialization and File Change Notification;
	 *   '03' = SIM Initialization;
	 *   '04' = SIM Reset;
	 *   '05' to 'FF' = reserved values.
	 */
	public static final byte SIM_REFRESH_SIM_INIT_FULL_FILE_CHANGE = 0x00;
	public static final byte SIM_REFRESH_FILE_CHANGE = 0x01;
	public static final byte SIM_REFRESH_SIM_INIT_FILE_CHANGE = 0x02;
	public static final byte SIM_REFRESH_SIM_INIT = 0x03;
	public static final byte SIM_REFRESH_SIM_RESET = 0x04;

	/* Run the Proactive SIM REFRESH command for the FID_EF_IMSI. */
	private void refreshIMSI()
	{
		/* See ETSI TS 101 267 / 3GPP TS 11.14 section 6.4.7.1 "EF IMSI changing procedure":
		 * Valid qualifiers are SIM_REFRESH_SIM_INIT_FILE_CHANGE and SIM_REFRESH_SIM_INIT_FULL_FILE_CHANGE.
		 */
		ProactiveHandler proHdlr = ProactiveHandler.getTheHandler();
		proHdlr.init((byte)PRO_CMD_REFRESH, SIM_REFRESH_SIM_INIT_FULL_FILE_CHANGE, DEV_ID_ME);
		proHdlr.send();
	}
}