imsi-change: import from imsi-pseudonymization
Patch-by: nhofmeyr, osmith
This commit is contained in:
parent
d102599fcf
commit
d3d776e26c
|
@ -0,0 +1,3 @@
|
||||||
|
build/
|
||||||
|
test/
|
||||||
|
.sim-keys
|
|
@ -0,0 +1,202 @@
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
|
@ -0,0 +1,71 @@
|
||||||
|
SIMTOOLS_DIR = ../../sim-tools
|
||||||
|
|
||||||
|
APPLET_AID = 0xd0:0x70:0x02:0xca:0x44:0x90:0x01:0x01
|
||||||
|
APPLET_NAME = org.osmocom.IMSIChange.IMSIChange
|
||||||
|
PACKAGE_AID = 0xd0:0x70:0x02:0xCA:0x44:0x90:0x01
|
||||||
|
PACKAGE_NAME = org.osmocom.IMSIChange
|
||||||
|
PACKAGE_VERSION = 1.0
|
||||||
|
|
||||||
|
SOURCES = \
|
||||||
|
src/org/osmocom/IMSIChange/Bytes.java \
|
||||||
|
src/org/osmocom/IMSIChange/MobileIdentity.java \
|
||||||
|
src/org/osmocom/IMSIChange/IMSIChange.java \
|
||||||
|
$(NULL)
|
||||||
|
|
||||||
|
CAP_FILE = build/javacard/org/osmocom/IMSIChange/javacard/IMSIChange.cap
|
||||||
|
|
||||||
|
include ./applet-project.mk
|
||||||
|
|
||||||
|
.PHONY: flash
|
||||||
|
flash: classes
|
||||||
|
$(eval MODULE_AID := $(shell echo $(APPLET_AID) | sed 's/0x//g' | sed 's/\://g'))
|
||||||
|
$(eval INSTANCE_AID := $(shell echo $(APPLET_AID) | sed 's/0x//g' | sed 's/\://g'))
|
||||||
|
. $$PWD/.sim-keys && $(SIMTOOLS_DIR)/bin/shadysim \
|
||||||
|
--pcsc \
|
||||||
|
-l $(CAP_FILE) \
|
||||||
|
-i $(CAP_FILE) \
|
||||||
|
--enable-sim-toolkit \
|
||||||
|
--access-domain=00 \
|
||||||
|
--module-aid $(MODULE_AID) \
|
||||||
|
--instance-aid $(INSTANCE_AID) \
|
||||||
|
--nonvolatile-memory-required 0100 \
|
||||||
|
--volatile-memory-for-install 0100 \
|
||||||
|
--max-menu-entry-text 21 \
|
||||||
|
--max-menu-entries 01 \
|
||||||
|
--kic "$$KIC1" \
|
||||||
|
--kid "$$KID1"
|
||||||
|
|
||||||
|
.PHONY: remove
|
||||||
|
remove:
|
||||||
|
. $$PWD/.sim-keys && $(SIMTOOLS_DIR)/bin/shadysim \
|
||||||
|
--pcsc \
|
||||||
|
-d "$$(echo $(PACKAGE_AID) | sed 's/0x//g' | sed 's/\://g')" \
|
||||||
|
--kic "$$KIC1" \
|
||||||
|
--kid "$$KID1"
|
||||||
|
|
||||||
|
.PHONY: list
|
||||||
|
list:
|
||||||
|
. $$PWD/.sim-keys && $(SIMTOOLS_DIR)/bin/shadysim \
|
||||||
|
--pcsc \
|
||||||
|
--list-applets \
|
||||||
|
--kic "$$KIC1" \
|
||||||
|
--kid "$$KID1"
|
||||||
|
|
||||||
|
.PHONY: delete
|
||||||
|
delete: remove
|
||||||
|
|
||||||
|
.PHONY: reflash
|
||||||
|
reflash:
|
||||||
|
$(MAKE) remove
|
||||||
|
$(MAKE) flash
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test:
|
||||||
|
mkdir -p ./test/classes
|
||||||
|
javac -target 1.1 -source 1.3 -classpath test/classes -g -d ./test/classes src/org/osmocom/IMSIChange/Bytes.java
|
||||||
|
javac -target 1.1 -source 1.3 -classpath test/classes -g -d ./test/classes src/org/osmocom/IMSIChange/MobileIdentity.java
|
||||||
|
javac -target 1.1 -source 1.3 -classpath test/classes -g -d ./test/classes src/org/osmocom/IMSIChange/Test.java
|
||||||
|
java -classpath test/classes org.osmocom.IMSIChange.Test
|
||||||
|
|
||||||
|
.PHONY: check
|
||||||
|
check: test
|
|
@ -0,0 +1,26 @@
|
||||||
|
# IMSI change SIM applet
|
||||||
|
|
||||||
|
Display and change the IMSI of the SIM. This is a standalone version of a debug
|
||||||
|
feature in the more complex IMSI Pseudonymization applet. To be used as example
|
||||||
|
code to build other applets.
|
||||||
|
|
||||||
|
### How to flash
|
||||||
|
|
||||||
|
```
|
||||||
|
$ cp .sim-keys.example .sim-keys
|
||||||
|
$ nvim .sim-keys # adjust KIC1, KID1
|
||||||
|
$ make flash
|
||||||
|
```
|
||||||
|
|
||||||
|
Before flashing a second time, remove the sim applet:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ make remove
|
||||||
|
```
|
||||||
|
|
||||||
|
### Related
|
||||||
|
|
||||||
|
* [IMSI Pseudonymization](https://osmocom.org/projects/imsi-pseudo/wiki)
|
||||||
|
* [Shadysimply in Osmocom wiki](https://osmocom.org/projects/cellular-infrastructure/wiki/Shadysimpy)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
BUILD_DIR = ./build
|
||||||
|
BUILD_CLASSES_DIR = $(BUILD_DIR)/classes
|
||||||
|
BUILD_JAVACARD_DIR = $(BUILD_DIR)/javacard
|
||||||
|
JAVACARD_SDK_DIR ?= $(SIMTOOLS_DIR)/javacard
|
||||||
|
JAVACARD_EXPORT_DIR ?= $(JAVACARD_SDK_DIR)/api21_export_files
|
||||||
|
ifdef COMSPEC
|
||||||
|
CLASSPATH = $(JAVACARD_SDK_DIR)/lib/api21.jar;$(JAVACARD_SDK_DIR)/lib/sim.jar
|
||||||
|
else
|
||||||
|
CLASSPATH = $(JAVACARD_SDK_DIR)/lib/api21.jar:$(JAVACARD_SDK_DIR)/lib/sim.jar
|
||||||
|
endif
|
||||||
|
JFLAGS = -target 1.1 -source 1.3 -g -d $(BUILD_CLASSES_DIR) -classpath "$(BUILD_CLASSES_DIR):$(CLASSPATH)"
|
||||||
|
JAVA ?= java
|
||||||
|
JC ?= javac
|
||||||
|
|
||||||
|
.SUFFIXES: .java .class
|
||||||
|
.java.class:
|
||||||
|
@mkdir -p $(BUILD_CLASSES_DIR)
|
||||||
|
@mkdir -p $(BUILD_JAVACARD_DIR)
|
||||||
|
$(JC) $(JFLAGS) $*.java
|
||||||
|
|
||||||
|
.PHONY: jar
|
||||||
|
jar: classes
|
||||||
|
$(JAVA) -jar $(JAVACARD_SDK_DIR)/bin/converter.jar \
|
||||||
|
-d $(BUILD_JAVACARD_DIR) \
|
||||||
|
-classdir $(BUILD_CLASSES_DIR) \
|
||||||
|
-exportpath $(JAVACARD_EXPORT_DIR) \
|
||||||
|
-applet $(APPLET_AID) $(APPLET_NAME) \
|
||||||
|
$(PACKAGE_NAME) $(PACKAGE_AID) $(PACKAGE_VERSION)
|
||||||
|
|
||||||
|
default: jar
|
||||||
|
|
||||||
|
classes: $(SOURCES:.java=.class)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(RM) -rf $(BUILD_DIR)
|
||||||
|
|
||||||
|
install:
|
||||||
|
$(eval CAP_FILE := $(shell find $(BUILD_JAVACARD_DIR) -name *.cap))
|
||||||
|
$(eval MODULE_AID := $(shell echo $(APPLET_AID) | sed 's/0x//g' | sed 's/\://g'))
|
||||||
|
$(eval INSTANCE_AID := $(shell echo $(APPLET_AID) | sed 's/0x//g' | sed 's/\://g'))
|
||||||
|
$(SIMTOOLS_DIR)/bin/shadysim \
|
||||||
|
$(SHADYSIM_OPTIONS) \
|
||||||
|
-l $(CAP_FILE) \
|
||||||
|
-i $(CAP_FILE) \
|
||||||
|
--enable-sim-toolkit \
|
||||||
|
--module-aid $(MODULE_AID) \
|
||||||
|
--instance-aid $(INSTANCE_AID) \
|
||||||
|
--nonvolatile-memory-required 0100 \
|
||||||
|
--volatile-memory-for-install 0100 \
|
||||||
|
--max-menu-entry-text 10 \
|
||||||
|
--max-menu-entries 01
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* Copyright 2020 sysmocom s.f.m.c. GmbH
|
||||||
|
* SPDX-License-Identifier: Apache-2.0 */
|
||||||
|
package org.osmocom.IMSIChange;
|
||||||
|
|
||||||
|
public class Bytes {
|
||||||
|
public static byte nibble2hex(byte nibble)
|
||||||
|
{
|
||||||
|
nibble = (byte)(nibble & 0xf);
|
||||||
|
if (nibble < 0xa)
|
||||||
|
return (byte)('0' + nibble);
|
||||||
|
else
|
||||||
|
return (byte)('a' + nibble - 0xa);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] hexdump(byte data[])
|
||||||
|
{
|
||||||
|
byte res[] = new byte[(byte)(data.length*2)];
|
||||||
|
for (byte i = 0; i < data.length; i++) {
|
||||||
|
res[(byte)(i*2)] = nibble2hex((byte)(data[i] >> 4));
|
||||||
|
res[(byte)(i*2 + 1)] = nibble2hex(data[i]);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean equals(byte a[], byte b[])
|
||||||
|
{
|
||||||
|
if (a.length != b.length)
|
||||||
|
return false;
|
||||||
|
for (short i = 0; i < (short)a.length; i++) {
|
||||||
|
if (a[i] != b[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDigit(byte digits[])
|
||||||
|
{
|
||||||
|
for (short i = 0; i < (short)digits.length; i++) {
|
||||||
|
if (digits[i] < '0' || digits[i] > '9')
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] toStr(byte byte_nr)
|
||||||
|
{
|
||||||
|
byte str[];
|
||||||
|
short nr = byte_nr;
|
||||||
|
byte d;
|
||||||
|
byte l = 0;
|
||||||
|
if (nr < 0) {
|
||||||
|
l = 1;
|
||||||
|
nr = (short)-nr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nr > 99) {
|
||||||
|
l += 3;
|
||||||
|
d = 100;
|
||||||
|
}
|
||||||
|
else if (nr > 9) {
|
||||||
|
l += 2;
|
||||||
|
d = 10;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
str = new byte[1];
|
||||||
|
l += 1;
|
||||||
|
d = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte i = 0;
|
||||||
|
str = new byte[l];
|
||||||
|
if (byte_nr < 0)
|
||||||
|
str[i++] = '-';
|
||||||
|
|
||||||
|
while (d > 0) {
|
||||||
|
str[i++] = (byte)('0' + (nr / d));
|
||||||
|
nr %= d;
|
||||||
|
d /= 10;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
/* Copyright 2020 sysmocom s.f.m.c. GmbH
|
||||||
|
* SPDX-License-Identifier: Apache-2.0 */
|
||||||
|
package org.osmocom.IMSIChange;
|
||||||
|
|
||||||
|
public class MobileIdentity {
|
||||||
|
public static final byte MI_IMSI = 1;
|
||||||
|
|
||||||
|
/* Convert BCD-encoded digit into printable character
|
||||||
|
* \param[in] bcd A single BCD-encoded digit
|
||||||
|
* \returns single printable character
|
||||||
|
*/
|
||||||
|
public static byte bcd2char(byte bcd)
|
||||||
|
{
|
||||||
|
if (bcd < 0xa)
|
||||||
|
return (byte)('0' + bcd);
|
||||||
|
else
|
||||||
|
return (byte)('A' + (bcd - 0xa));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert BCD to string.
|
||||||
|
* The given nibble offsets are interpreted in BCD order, i.e. nibble 0 is bcd[0] & 0xf, nibble 1 is bcd[0] >> 4, nibble
|
||||||
|
* 3 is bcd[1] & 0xf, etc..
|
||||||
|
* \param[out] dst Output byte array.
|
||||||
|
* \param[in] dst_ofs Where to start writing in dst.
|
||||||
|
* \param[in] dst_len How many bytes are available at dst_ofs.
|
||||||
|
* \param[in] bcd Binary coded data buffer.
|
||||||
|
* \param[in] start_nibble Offset to start from, in nibbles.
|
||||||
|
* \param[in] end_nibble Offset to stop before, in nibbles.
|
||||||
|
* \param[in] allow_hex If false, return false if there are digits other than 0-9.
|
||||||
|
* \returns true on success, false otherwise
|
||||||
|
*/
|
||||||
|
public static boolean bcd2str(byte dst[], byte dst_ofs, byte dst_len,
|
||||||
|
byte bcd[], byte start_nibble, byte end_nibble, boolean allow_hex)
|
||||||
|
{
|
||||||
|
byte nibble_i;
|
||||||
|
byte dst_i = dst_ofs;
|
||||||
|
byte dst_end = (byte)(dst_ofs + dst_len);
|
||||||
|
boolean rc = true;
|
||||||
|
|
||||||
|
for (nibble_i = start_nibble; nibble_i < end_nibble && dst_i < dst_end; nibble_i++, dst_i++) {
|
||||||
|
byte nibble = bcd[(byte)nibble_i >> 1];
|
||||||
|
if ((nibble_i & 1) != 0)
|
||||||
|
nibble >>= 4;
|
||||||
|
nibble &= 0xf;
|
||||||
|
|
||||||
|
if (!allow_hex && nibble > 9)
|
||||||
|
rc = false;
|
||||||
|
|
||||||
|
dst[dst_i] = bcd2char(nibble);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] mi2str(byte mi[])
|
||||||
|
{
|
||||||
|
/* The IMSI byte array by example:
|
||||||
|
* 08 99 10 07 00 00 10 74 90
|
||||||
|
*
|
||||||
|
* This is encoded according to 3GPP TS 24.008 10.5.1.4 Mobile
|
||||||
|
* Identity, short the Mobile Identity IEI:
|
||||||
|
*
|
||||||
|
* 08 length for the following MI, in bytes.
|
||||||
|
* 9 = 0b1001
|
||||||
|
* 1 = odd nr of digits
|
||||||
|
* 001 = MI type = IMSI
|
||||||
|
* 9 first IMSI digit (BCD)
|
||||||
|
* 0 second digit
|
||||||
|
* 1 third
|
||||||
|
* ...
|
||||||
|
* 0 14th digit
|
||||||
|
* 9 15th and last digit
|
||||||
|
*
|
||||||
|
* If the IMSI had an even number of digits:
|
||||||
|
*
|
||||||
|
* 08 98 10 07 00 00 10 74 f0
|
||||||
|
*
|
||||||
|
* 08 length for the following MI, in bytes.
|
||||||
|
* 8 = 0b0001
|
||||||
|
* 0 = even nr of digits
|
||||||
|
* 001 = MI type = IMSI
|
||||||
|
* 9 first IMSI digit
|
||||||
|
* 0 second digit
|
||||||
|
* 1 third
|
||||||
|
* ...
|
||||||
|
* 0 14th and last digit
|
||||||
|
* f filler
|
||||||
|
*/
|
||||||
|
byte bytelen = mi[0];
|
||||||
|
byte mi_type = (byte)(mi[1] & 0xf);
|
||||||
|
boolean odd_nr_of_digits = ((mi_type & 0x08) != 0);
|
||||||
|
byte start_nibble = 2 + 1; // 2 to skip the bytelen, 1 to skip the mi_type
|
||||||
|
byte end_nibble = (byte)(2 + bytelen * 2 - (odd_nr_of_digits ? 0 : 1));
|
||||||
|
byte str[] = new byte[end_nibble - start_nibble];
|
||||||
|
bcd2str(str, (byte)0, (byte)str.length, mi, start_nibble, end_nibble, true);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte char2bcd(byte c)
|
||||||
|
{
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
return (byte)(c - '0');
|
||||||
|
else if (c >= 'A' && c <= 'F')
|
||||||
|
return (byte)(0xa + (c - 'A'));
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
return (byte)(0xa + (c - 'a'));
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] str2mi(byte str[], byte mi_type, byte min_buflen)
|
||||||
|
{
|
||||||
|
boolean odd_digits = ((str.length & 1) != 0);
|
||||||
|
/* 1 nibble of mi_type.
|
||||||
|
* str.length nibbles of MI BCD.
|
||||||
|
*/
|
||||||
|
byte mi_nibbles = (byte)(1 + str.length);
|
||||||
|
byte mi_bytes = (byte)(mi_nibbles / 2 + ((mi_nibbles & 1) != 0? 1 : 0));
|
||||||
|
/* 1 byte of total MI length in bytes, plus the MI nibbles */
|
||||||
|
byte buflen = (byte)(1 + mi_bytes);
|
||||||
|
/* Fill up with 0xff to the requested buffer size */
|
||||||
|
if (buflen < min_buflen)
|
||||||
|
buflen = min_buflen;
|
||||||
|
byte buf[] = new byte[buflen];
|
||||||
|
|
||||||
|
for (byte i = 0; i < buf.length; i++)
|
||||||
|
buf[i] = (byte)0xff;
|
||||||
|
|
||||||
|
/* 1 byte of following MI length in bytes */
|
||||||
|
buf[0] = mi_bytes;
|
||||||
|
|
||||||
|
/* first MI byte: low nibble has the MI type and odd/even indicator bit,
|
||||||
|
* high nibble has the first BCD digit.
|
||||||
|
*/
|
||||||
|
mi_type = (byte)(mi_type & 0x07);
|
||||||
|
if (odd_digits)
|
||||||
|
mi_type |= 0x08;
|
||||||
|
buf[1] = (byte)((char2bcd(str[0]) << 4) + mi_type);
|
||||||
|
|
||||||
|
/* fill in the remaining MI nibbles */
|
||||||
|
byte str_i = 1;
|
||||||
|
for (byte mi_i = 1; mi_i < mi_bytes; mi_i++) {
|
||||||
|
byte data = char2bcd(str[str_i++]);
|
||||||
|
if (str_i < str.length)
|
||||||
|
data |= char2bcd(str[str_i++]) << 4;
|
||||||
|
else
|
||||||
|
data |= 0xf0;
|
||||||
|
buf[1 + mi_i] = data;
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/* Copyright 2020 sysmocom s.f.m.c. GmbH
|
||||||
|
* SPDX-License-Identifier: Apache-2.0 */
|
||||||
|
package org.osmocom.IMSIChange;
|
||||||
|
import org.osmocom.IMSIChange.*;
|
||||||
|
|
||||||
|
public class Test {
|
||||||
|
private static byte nibble2hex(byte nibble)
|
||||||
|
{
|
||||||
|
nibble = (byte)(nibble & 0xf);
|
||||||
|
if (nibble < 0xa)
|
||||||
|
return (byte)('0' + nibble);
|
||||||
|
else
|
||||||
|
return (byte)('a' + nibble - 0xa);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] hexdump(byte data[])
|
||||||
|
{
|
||||||
|
byte res[] = new byte[(byte)(data.length*2)];
|
||||||
|
for (byte i = 0; i < data.length; i++) {
|
||||||
|
res[(byte)(i*2)] = nibble2hex((byte)(data[i] >> 4));
|
||||||
|
res[(byte)(i*2 + 1)] = nibble2hex(data[i]);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String hexdumpStr(byte data[])
|
||||||
|
{
|
||||||
|
return new String(hexdump(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String[] imsis = {
|
||||||
|
"123456",
|
||||||
|
"1234567",
|
||||||
|
"12345678",
|
||||||
|
"123456789",
|
||||||
|
"1234567890",
|
||||||
|
"12345678901",
|
||||||
|
"123456789012",
|
||||||
|
"1234567890123",
|
||||||
|
"12345678901234",
|
||||||
|
"123456789012345",
|
||||||
|
"1234567890123456",
|
||||||
|
};
|
||||||
|
|
||||||
|
private static void test_str2mi2str()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < imsis.length; i++) {
|
||||||
|
byte str[] = imsis[i].getBytes();
|
||||||
|
byte mi[] = MobileIdentity.str2mi(str, MobileIdentity.MI_IMSI, (byte)9);
|
||||||
|
byte str_from_mi[] = MobileIdentity.mi2str(mi);
|
||||||
|
System.out.print("IMSI " + new String(str) + " --> MI " + hexdumpStr(mi) + " --> IMSI "
|
||||||
|
+ new String(str_from_mi));
|
||||||
|
if (Bytes.equals(str, str_from_mi))
|
||||||
|
System.out.println(" (ok)");
|
||||||
|
else
|
||||||
|
System.out.println(" ERROR!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void test_toStr()
|
||||||
|
{
|
||||||
|
byte nr = -128;
|
||||||
|
while (true) {
|
||||||
|
System.out.println("" + nr + " = '" + new String(Bytes.toStr(nr)) + "'");
|
||||||
|
if (nr == 127)
|
||||||
|
break;
|
||||||
|
nr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String args[]){
|
||||||
|
test_str2mi2str();
|
||||||
|
test_toStr();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue