Visual Studio Code利用¶
Visual Studio Codeを用いてIMEI・IMSI・ICCIDを取得して applet_rst へ送信するアプレットの作成手順を示します
注釈
- 各ソフトウェアのライセンス規定に従ってご利用ください
- アプレットのインストール環境構築 作業後に実施してください
JCDK (Java Card Development Kit)の準備¶
- Java Card Development Kit Tools (Version 3.1)をインストールします
- https://www.oracle.com/java/technologies/javacard-sdk-downloads.html から、ファイルをダウンロードします。
- Java Card Development Kit Tools (06_July_2021)
- java_card_tools-win-bin-b_17-06_jul_2021.zip
- ユーザ登録が必要です。
- 上記より古いバージョン (Java Card Classic Development Kit 3.0.5u4など)は使用できません。
ダウンロードしたzipファイルをWindowsのホームパス (フォルダ
C:\Users\ユーザ名
)に展開してください。
- Java Card Classic Platform Specification 3.0.5 をインストールします。
- https://www.oracle.com/java/technologies/javacard-downloads.html から、3.0.5互換性のあるJCDKをダウンロードします。
- ダウンロードしたzipファイルを、1.で展開したフォルダにコピーします。
- 下図のようにJCDKがWindowsのホームパス (フォルダ
C:\Users\ユーザ名
)に展開されていること、そのフォルダにjava_card_kit-classic-3_0_5-ga-spec-doc-b33-03_jun_2015.zip
があること、の2点を確認してください。
- 下図のようにJCDKがWindowsのホームパス (フォルダ
環境変数の設定¶
ctrl+shift+@
を押してコマンドプロンプトを開きます- javacをUTF-8エンコーディングでビルドするため、ユーザー環境変数
JAVA_TOOL_OPTIONS
に-Dfile.encoding=UTF8
を設定します C:\Users\anyuser>setx JAVA_TOOL_OPTIONS -Dfile.encoding=UTF8
- javacをUTF-8エンコーディングでビルドするため、ユーザー環境変数
- 環境変数の状態を確認します
C:\Users\anyuser>echo %JAVA_TOOL_OPTIONS% -Dfile.encoding=UTF8
プロジェクトの新規作成¶
- APIパッケージ(USIM API・UICC API)をダウンロードします
- SIMに搭載するアプレットから利用するAPIパッケージとJavaDocは、3GPPおよびETSIの規格書の付録として配布されていますので、ダウンロードして展開します。
- バッチファイル
lib/download_apifiles.bat
を配置します。 :: :: download *.exp, *.jar, and JavaDoc from 3GPP and ETSI and extract them :: @echo off SETLOCAL cd %~dp0 :: 3GPP TS 31.130 v13.0.0 curl -fOL https://www.3gpp.org/ftp/Specs/archive/31_series/31.130/31130-d30.zip if ERRORLEVEL 1 exit /b :: ETSI TS 102 241 v13.0.0 curl -fOL https://www.etsi.org/deliver/etsi_ts/102200_102299/102241/13.00.00_60/ts_102241v130000p0.zip if ERRORLEVEL 1 exit /b for %%F in (%~dp0\*.zip) do ( powershell -command "Expand-Archive -Force '%%F'" ) ENDLOCAL
- バッチファイル
Terminalを起動します
アプレット書き込みツールの設定と機器動作確認¶
アプレットをSIMへインストールするためのツールを設定し、機器動作確認を行います。
以下のリンクからバッチファイルを取得して
tools/
に保存します。ファイル 機能 tools/scp03keys.bat
SIM毎に異なるSCP03鍵 (OEMKIC, OEMKID, OEMKIK) を記載するファイル tools/install_applet.bat
サンプルアプレットのインストールおよび、インストールパラメータの設定 tools/uninstall_applet.bat
サンプルアプレットのアンインストール tools/list_applet.bat
SIMにインストールされているアプレット等の一覧を表示 tools/find_card_readers.bat
ICカードリーダおよびSIMの接続状況を表示 tools/download_tools.bat
GlobalPlatformPro, APDU4JをGitHubからダウンロードする tools/envpath.bat
環境変数JAVA_HOMEなどを一時的に設定する 注釈
上記のバッチファイルは GlobalPlatformPro (オープンソースソフトウェア)を利用してアプレットのインストール等を行います。
- Terminalを開き(vscodeのショートカット: ctrl+shift+@), バッチファイル
tools/download_tools.bat
を実行します - このバッチファイルは
tools/gp.jar
,tools/apdu4j.jar
を GitHubからダウンロードします
- このバッチファイルは
- Terminalを開き(vscodeのショートカット: ctrl+shift+@), バッチファイル
- SIMをICカードリーダに装着し、バッチファイル
tools/find_card_readers.bat
を実行します。 [*]
が表示される行が1行のみ存在することを確認してください。[*]
が表示されない場合- ICカードリーダおよびSIMの装着状況を確認してください。
[*]
が複数行表示される場合- 他のSIMやICカード等がPCに装着されていますので 必ず取り外してください 。
- 誤ってアプレットを書き込むことにより破損する恐れがあります。
- SIMをICカードリーダに装着し、バッチファイル
ファイル
tools/scp03keys.bat
を編集し、ICカードリーダに装着したSIMのSCP03鍵(OEMKIC, OEMKID, OEMKIK)の値を入力して保存します。:: :: アプレットをインストールするSIMカードの鍵情報を記載してください。 :: set OEM_ENC=03030303030303030303030303030303 set OEM_MAC=04040404040404040404040404040404 set OEM_KEK=05050505050505050505050505050505 set OEM_SSD=A0000001156000000000000000011001
- ファイル
tools/list_applet.bat
を実行します - AID値が表示されることを確認してください。
- AID値が表示できない場合は、
tools/scp03keys.bat
に記載した値を確認してください。 - こちら から鍵の値を確認できます。
- SCP03鍵の値が誤ったままで何度も操作を繰り返すと、SIMを破損する恐れがあります。
- ファイル
Antビルドファイルの作成¶
Apache Antを利用してアプレットをビルドする設定を行います。
ビルドファイル
build.xml
を下記内容で作成します。<?xml version="1.0" encoding="UTF-8" standalone="no"?> <project default="Verification" basedir="."> <property file="build.properties"/> <property environment="env"/> <!-- The Java Card Development Kit Tools (JCDK Tools) --> <property name="jc_home_tools" location="${env.HOMEDRIVE}${env.HOMEPATH}/java_card_tools-win-bin-b_17-06_jul_2021"/> <!-- Project paths --> <property name="path.suncap" location="suncap"/> <property name="path.class" location="class"/> <property name="path.exports" location="exports"/> <property name="path.src" location="src"/> <!-- Tools paths --> <property name="converter.sun" location="${jc_home_tools}/bin/converter.bat"/> <property name="verify.sun" location="${jc_home_tools}/bin/verifycap.bat"/> <target name="extractExp" description="Extract *.exp from library jar"> <unzip dest="${path.exports}"> <patternset> <exclude name="**/META-INF/*"/> </patternset> <fileset dir="."> <include name="lib/**/*.jar"/> </fileset> </unzip> </target> <target name="Compiling" depends="extractExp" description="Compiling java source to class file..."> <mkdir dir="${path.suncap}"/> <mkdir dir="${path.class}"/> <!-- Deletes all files and subdirectories of "class", without "class" itself --> <delete includeemptydirs="true" verbose="false"> <fileset dir="${path.class}" includes="**/*"/> </delete> <!--Compile *.Java to *.Class--> <javac executable="${java.home}" verbose="false" debug="false" destdir="${path.class}" source="7" target="7" compiler="javac1.8" failonerror="true" includeantruntime="false"> <src path="${path.src}"/> <classpath> <pathelement location="${jc_home_tools}/lib/api_classic-3.0.5.jar"/> <pathelement path="${path.exports}"/> </classpath> </javac> </target> <target name="Conversion" depends="Compiling" description="Converting class file to cap file..."> <!-- Deletes all files and subdirectories of "suncap", without "suncap" itself --> <delete includeemptydirs="true" verbose="false"> <fileset dir="${path.suncap}" includes="**/*"/> </delete> <!--Convert *.Class to *.cap--> <exec executable="${converter.sun}" failonerror="true"> <env key="JAVA_HOME" value="${java.home}"/> <arg line="-v "/> <arg line="-target 3.0.5"/> <arg line="-out CAP EXP"/> <arg line="-exportpath ${path.exports}"/> <arg line="-classdir ${path.class}"/> <arg line="-d ${path.suncap}"/> <arg line="-applet ${app.aid.class} ${app.name.package}.${app.name.class}"/> <arg line="${app.name.package}"/> <arg line="${app.aid.package}"/> <arg line="${app.ver.package}"/> </exec> <copy tofile="${path.suncap}/${app.name.cap}.cap" file="${path.suncap}/${app.path.package}/javacard/${app.name.cap}.cap"/> <copy tofile="${path.suncap}/${app.name.cap}.exp" file="${path.suncap}/${app.path.package}/javacard/${app.name.cap}.exp"/> <delete dir="${path.suncap}" verbose="false" includeemptydirs="true" excludes=" *.cap *.exp .gitkeep"/> </target> <target name="Verification" depends="Conversion" description="Verifying cap file..."> <path id="path.jcexportfiles"> <fileset dir="${path.exports}" includes="**/*.exp"/> </path> <pathconvert property="jcexportfiles" refid="path.jcexportfiles" pathsep=" "/> <exec executable="${verify.sun}" failonerror="true"> <env key="JAVA_HOME" value="${java.home}"/> <arg line="-target 3.0.5"/> <arg line="${jcexportfiles}"/> <arg line="${path.suncap}/${app.name.cap}.exp"/> <arg line="${path.suncap}/${app.name.cap}.cap"/> </exec> </target> </project>
ファイル
build.properties
を下記内容で作成します。app.path.package=internal\\sim\\sample_applet app.name.cap=sample_applet app.name.package=internal.sim.sample_applet app.name.class=SampleApplet app.aid.package=0xA0:0x00:0x00:0x01:0x15:0x70:0x00:0x00:0x00:0x00:0x00:0x00:0x44:0x45:0x56:0x01 app.aid.class=0xA0:0x00:0x00:0x01:0x15:0x70:0x00:0x00:0x00:0x00:0x00:0x00:0x44:0x45:0x56:0x02 app.ver.package=1.0
最小構成のアプレットの作成とビルド¶
ここまでの操作でプロジェクト設定が完了したので、最小構成のアプレットを作成してビルドしてみます。
ソースファイル
src/internal/sim/sample_applet/SampleApplet.java
を下記内容で作成します。package internal.sim.sample_applet; import javacard.framework.*; import uicc.toolkit.*; public class SampleApplet extends Applet implements ToolkitInterface, ToolkitConstants { public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException { SampleApplet sampleApplet = new SampleApplet(); sampleApplet.register(); } private SampleApplet() { } public Shareable getShareableInterfaceObject(AID clientAID, byte parameter) { if (clientAID == null) { return this; } return null; } @Override public void process(APDU apdu) throws ISOException { } @Override public void processToolkit(short event) throws ToolkitException { } }
このソースファイルで実装しているコンストラクタと4個のメソッドが、SIMアプレットの最小限の構造になります。 各メソッドの機能は次の通りです。
メソッド 機能 install(bArray, ..) - アプレットのインストール時に1回だけ実行されるエントリポイントです。
- Appletのインスタンスを作成して .register() を呼ぶことが必須機能です。
- .register()の呼び出しが成功し、本メソッドが例外を発生させなければ、アプレットのインストールが完了します。
- その他のインストール時処理を行います(各種オブジェクトインスタンスの作成、リソース確保、インストールパラメータの受け取りなど)
processToolkit(event) - 端末(スマートフォンや通信モジュール等)上のSIM Toolkitソフトウェアから、SIM上のUSIM Application宛にEnvelope Commandなどのイベントを受け取ると呼び出されるメソッドです。
- SIMアプレットの機能は、processToolkitメソッドに各種イベントを処理するハンドラを実装することによって構築します。
process(apdu) - USIM Applicationを経由せずにアプレット宛のAPDUコマンドを受け取った場合に呼び出されます。
- 本サンプルアプレットでは使用しません。
getShareableInterfaceObject() - SIMカード上の別のアプレット(USIM Application)からこのアプレットの processToolkit メソッドを呼び出すために必要なインタフェースです。
- 特に変更を加える必要はありません。
- コマンドプロンプトから ant と打つとビルドが始まり、Verification が正常終了すると
BUILD SUCCESSFUL
と出力されます PS C:\anyuser\tmp> ant : BUILD SUCCESSFUL Total time: 1 second PS C:\anyuser\tmp>
- コマンドプロンプトから ant と打つとビルドが始まり、Verification が正常終了すると
アプレットのデバッグ手段の実装¶
SIMは極めて機微な情報を容易に取り出せないように格納するデバイスとして設計されている関係上、アプレットの実機デバッグを行うための機構やデバッガが用意されていません。
以下では、ETSI 102 223 などの規格に規定されている「Card Application Toolkit」の機能を利用して スマートフォンの画面上にメモリダンプ等を表示する処理を実装し、いわゆるprintデバッグが行えるようにします。
下記のソースファイルをディレクトリ
src/internal/sim/sample_applet/
に追加します。ソースファイル 主な実装機能 ByteUtil.java
byte配列を16進数文字列に変換する処理など DiagUtil.java
UICC APIを使用してスマートフォン上で動作するSIM Toolkitアプリへ文字列を送り、ダイアログを表示させる 下記のように
src/internal/sim/sample_applet/SampleApplet.java
を変更します。src/internal/sim/sample_applet/SampleApplet.java
(変更後のファイル)--- 1/SampleApplet.java 2023-06-29 14:22:30.218688600 +0900 +++ 2/SampleApplet.java 2023-06-29 14:22:30.548822900 +0900 @@ -1,17 +1,45 @@ package internal.sim.sample_applet; import javacard.framework.*; +import javacardx.framework.math.BCDUtil; import uicc.toolkit.*; public class SampleApplet extends Applet implements ToolkitInterface, ToolkitConstants { + private ToolkitRegistry toolkitRegistry; + private byte menuItem1; + + static final byte[] nvramText = {'N', 'V', 'R', 'A', 'M', ':', ' '}; + static final byte[] ramText = {'R', 'A', 'M', ':', ' '}; + static final byte[] menuItem1Text = {'D', 'e', 'b', 'u', 'g'}; + + private byte[] debugBuffer; + private short[] debugMemBuffer; + private DiagUtil diag; + + private byte[] bcdBuffer; + private byte[] tmpBuffer; + public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException { SampleApplet sampleApplet = new SampleApplet(); sampleApplet.register(); + + sampleApplet.initUiccToolkit(); } - private SampleApplet() { + private void initUiccToolkit() { + toolkitRegistry = ToolkitRegistrySystem.getEntry(); + menuItem1 = toolkitRegistry.initMenuEntry(menuItem1Text, (short) 0, (short) menuItem1Text.length, + PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0); + } + + private SampleApplet() { + diag = new DiagUtil(); + debugBuffer = JCSystem.makeTransientByteArray((short) 16, JCSystem.CLEAR_ON_RESET); + debugMemBuffer = JCSystem.makeTransientShortArray((short) 2, JCSystem.CLEAR_ON_RESET); + bcdBuffer = JCSystem.makeTransientByteArray((short) 10, JCSystem.CLEAR_ON_RESET); + tmpBuffer = JCSystem.makeTransientByteArray((short) 64, JCSystem.CLEAR_ON_RESET); } public Shareable getShareableInterfaceObject(AID clientAID, byte parameter) { @@ -28,6 +56,53 @@ @Override public void processToolkit(short event) throws ToolkitException { + // 本メソッドが例外を発生させても外部から観測できないため、デバッグが困難になります。 + // 必ず全ての例外をキャッチして、デバッグ用に記録するようにします。 + try { + processToolkitEvent(event); + } catch (UserException e) { + Util.setShort(debugBuffer, (short) 4, e.getReason()); + } catch (ToolkitException e) { + Util.setShort(debugBuffer, (short) 6, e.getReason()); + throw e; + } catch (Exception e) { + //その他の例外の発生回数を記録 + short i = Util.getShort(debugBuffer, (short) 2); + Util.setShort(debugBuffer, (short) 2, (short) (i + 1)); + } + } + + public void processToolkitEvent(short event) throws ToolkitException, UserException { + if (event == EVENT_MENU_SELECTION) { + // ユーザがSIM Toolkitアプリを端末上で開き、メニュー項目をタップすると呼ばれる + EnvelopeHandler envHdlr = EnvelopeHandlerSystem.getTheHandler(); + byte selectedItemId = envHdlr.getItemIdentifier(); + + if (selectedItemId == menuItem1) { + DiagUtil.text(menuItem1Text); //'Debug' + diag.displayBytes(debugBuffer, (short) 0, (short) debugBuffer.length); + + short pos, bcdBytes; + //NVRAMの残量を表示 + JCSystem.getAvailableMemory(debugMemBuffer, (short) 0, JCSystem.MEMORY_TYPE_PERSISTENT); + Util.setShort(bcdBuffer, (short) 0, debugMemBuffer[0]); + Util.setShort(bcdBuffer, (short) 2, debugMemBuffer[1]); + bcdBytes = BCDUtil.convertToBCD(bcdBuffer, (short) 0, (short) 4, bcdBuffer, (short) 0); + pos = Util.arrayCopy(nvramText, (short) 0, tmpBuffer, (short) 0, (short) nvramText.length); + pos = ByteUtil.bcdToCharArray(bcdBuffer, bcdBytes, tmpBuffer, pos); + diag.text(tmpBuffer, (short) 0, pos); + + //RAMの残量を表示 + JCSystem.getAvailableMemory(debugMemBuffer, (short) 0, JCSystem.MEMORY_TYPE_TRANSIENT_RESET); + Util.setShort(bcdBuffer, (short) 0, debugMemBuffer[0]); + Util.setShort(bcdBuffer, (short) 2, debugMemBuffer[1]); + bcdBytes = BCDUtil.convertToBCD(bcdBuffer, (short) 0, (short) 4, bcdBuffer, (short) 0); + pos = Util.arrayCopy(ramText, (short) 0, tmpBuffer, (short) 0, (short) ramText.length); + pos = ByteUtil.bcdToCharArray(bcdBuffer, bcdBytes, tmpBuffer, pos); + diag.text(tmpBuffer, (short) 0, pos); + } + } } + }
変更箇所では以下の処理を実装しています。
メソッド 処理内容 SampleApplet() (コンストラクタ) テンポラリバッファとして使用するRAM領域を確保する (JCSystem.makeTransientByteArray()) install(), initUiccToolkit() メニュー項目 Debug
を登録するprocessToolkit(), processToolkitEvent() スマートフォン画面上でメニュー項目 Debug
をタップすると イベントEVENT_MENU_SELECTION
が発生するので、これを受け取って byte配列debugBuffer
の内容および、 NVRAM領域・RAM領域の残バイト数をダイアログ表示するprocessToolkit() 例外(Exception)をすべて捕捉し、例外発生回数などをbyte配列 debugBuffer
に記録する上記の変更を行ったら、アプレットがビルドできることを確認してください。 (View → Tool Windows → Antを開き、
Verification
ターゲットを実行する)
アプレットの書き込みと動作確認¶
以下では、前項でビルドしたアプレットをSIMにインストールして、動作確認を行います。
コマンドプロンプトを起動(ショートカット: ctrl+shift+@)し、 バッチファイル
tools\install_applet.bat
を実行します。アプレットのインストールが正常完了すると、下記例のように
CAP loaded
,Installed OK
が表示されます。SIMをスマートフォンに装着し電源を入れます。
アプリ「SIM Toolkit」を起動します。
注釈
Androidの場合、スマートフォンの起動後数分待ってからSIM Toolkitアプリを起動してください。(機種によっては、起動直後のアプリ動作が不安定なものがあります)
注釈
アプレット側で
toolkitRegistry.initMenuEntry()
によりメニュー項目を登録している場合のみ、SIM Toolkitアプリを起動できるようにスマートフォンが構成されます。メニュー項目
Debug
が表示されますので、タップします。下図のようにダイアログが数回表示され、byte配列
debugBuffer
の内容、NVRAMの残容量、RAMの残容量が確認できます。この機能を利用・改変することにより、以下のようなデバッグを行うことができます。
例外発生回数・発生有無の確認
- いわゆるprintデバッグ
- debugBufferに格納した数値や、DiagUtil.text()で表示させる文字列によって処理内容を確認します。
- NVRAM/RAMの残容量や、メモリリーク有無の確認
- 本サンプルアプレットと他のアプレットを同時にインストールしておくと、他のアプレットで発生したメモリリーク等の検出に使うこともできます。
IMEIの取得(端末ーアプレット間のインタフェース方法)¶
UICC API・USIM APIを利用して、端末 (スマートフォンや通信モジュール等) からセルラー回線の状態に関連する情報を得ることができます。
以下では、APIの実装例として、端末からIMEIを取得する機能をサンプルアプレットに追加します。
下記のようにSampleApplet.javaを変更します。
src/internal/sim/sample_applet/SampleApplet.java
(変更後のファイル)--- 2/SampleApplet.java 2023-06-29 14:22:30.548822900 +0900 +++ 3/SampleApplet.java 2023-06-29 14:22:30.908912600 +0900 @@ -8,10 +8,12 @@ private ToolkitRegistry toolkitRegistry; private byte menuItem1; + private byte menuItem2; static final byte[] nvramText = {'N', 'V', 'R', 'A', 'M', ':', ' '}; static final byte[] ramText = {'R', 'A', 'M', ':', ' '}; static final byte[] menuItem1Text = {'D', 'e', 'b', 'u', 'g'}; + static final byte[] menuItem2Text = {'T', 'e', 's', 't'}; private byte[] debugBuffer; private short[] debugMemBuffer; @@ -20,6 +22,9 @@ private byte[] bcdBuffer; private byte[] tmpBuffer; + private byte[] readBuffer; + private byte[] imeiBuffer; + public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException { SampleApplet sampleApplet = new SampleApplet(); sampleApplet.register(); @@ -32,6 +37,8 @@ menuItem1 = toolkitRegistry.initMenuEntry(menuItem1Text, (short) 0, (short) menuItem1Text.length, PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0); + menuItem2 = toolkitRegistry.initMenuEntry(menuItem2Text, (short) 0, (short) menuItem2Text.length, + PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0); } private SampleApplet() { @@ -40,6 +47,9 @@ debugMemBuffer = JCSystem.makeTransientShortArray((short) 2, JCSystem.CLEAR_ON_RESET); bcdBuffer = JCSystem.makeTransientByteArray((short) 10, JCSystem.CLEAR_ON_RESET); tmpBuffer = JCSystem.makeTransientByteArray((short) 64, JCSystem.CLEAR_ON_RESET); + + readBuffer = JCSystem.makeTransientByteArray((short) 256, JCSystem.CLEAR_ON_RESET); + imeiBuffer = JCSystem.makeTransientByteArray((short) 15, JCSystem.CLEAR_ON_RESET); } public Shareable getShareableInterfaceObject(AID clientAID, byte parameter) { @@ -101,8 +111,41 @@ pos = ByteUtil.bcdToCharArray(bcdBuffer, bcdBytes, tmpBuffer, pos); diag.text(tmpBuffer, (short) 0, pos); } + if (selectedItemId == menuItem2) { + loadIMEI(); + DiagUtil.text(imeiBuffer); + } } } + private void loadIMEI() throws ToolkitException, UserException { + short length = 0; + imeiBuffer[0] = 0; + + // 端末に コマンド PROVIDE LOCAL INFORMATION を送り、IMEIを取得する + // 参照規格: ETSI TS 102223 Clause 6.4.15, 6.6.15など + ProactiveHandler ph = ProactiveHandlerSystem.getTheHandler(); + ProactiveResponseHandler rh = ProactiveResponseHandlerSystem.getTheHandler(); + + ph.init(PRO_CMD_PROVIDE_LOCAL_INFORMATION, (byte) 0x01, DEV_ID_TERMINAL); + ph.send(); + + if (rh.getGeneralResult() == RES_CMD_PERF) { + length = rh.findAndCopyValue(TAG_IMEI, readBuffer, (short) 0); + } else { + UserException.throwIt((short) 0x7001); + } + if (length != 8) { + UserException.throwIt((short) 0x7002); + } + // IMEIのデータ形式を考慮して文字列に変換し、チェックデジットを計算する + // 参照規格: ETSI TS 102223 Clause 8.20, ETSI TS 124008 (3GPP TS 24.008), ETSI TS 123003 など + ByteUtil.nibbleSwap(readBuffer, (short) 0, length); + ByteUtil.bytesToHex(readBuffer, (short) 0, length, tmpBuffer, (short) 0); + short checkDigit = ByteUtil.calcCheckDigitByLuhn(tmpBuffer, (short) 1, (short) 14); + tmpBuffer[15] = (byte) (checkDigit + '0'); + Util.arrayCopyNonAtomic(tmpBuffer, (short) 1, imeiBuffer, (short) 0, (short) 15); + } + }
- 変更箇所では以下の機能を実装しています。
- メニュー項目
Test
を追加 - 端末にコマンド PROVIDE LOCAL INFORMATION を送り、IMEIを取得
- IMEIを文字列に変換する
- スマートフォンのSIM Toolkitアプリ上でメニュー項目
Test
をタップすると、IMEIをダイアログ表示する
- メニュー項目
注釈
PROVIDE LOCAL INFORMATION コマンドの詳細については、ソースコードコメントに記載した参照規格 (ETSI TS 102223など) を参照してください。
上記の変更を行ったら、アプレットがビルドできることを確認してください。 (View → Tool Windows → Antを開き、
Verification
ターゲットを実行する)
IMSIおよびICCIDの取得(SIMーアプレット間のインタフェース方法)¶
UICC API・USIM APIを利用して、SIM上の USIM Applicationが保持しているファイルシステムにアクセスすることができます。
以下では、APIの実装例として、IMSIおよびICCIDの取得機能をサンプルアプレットに追加します。
下記のように
SampleApplet.java
を変更します。src/internal/sim/sample_applet/SampleApplet.java
(変更後のファイル)--- 3/SampleApplet.java 2023-06-29 14:22:30.908912600 +0900 +++ 4/SampleApplet.java 2023-06-29 14:22:31.248869200 +0900 @@ -2,9 +2,17 @@ import javacard.framework.*; import javacardx.framework.math.BCDUtil; +import uicc.access.*; import uicc.toolkit.*; +import uicc.usim.access.USIMConstants; public class SampleApplet extends Applet implements ToolkitInterface, ToolkitConstants { + //SIMカード上で稼働中のUSIM ApplicationのAID値 + private static final byte[] usimAID = { + (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x87, (byte) 0x10, (byte) 0x02, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x89, (byte) 0x03, (byte) 0x02, (byte) 0x00, (byte) 0x00}; + private FileView uiccFileView; + private FileView usimAppFileView; private ToolkitRegistry toolkitRegistry; private byte menuItem1; @@ -24,6 +32,8 @@ private byte[] readBuffer; private byte[] imeiBuffer; + private byte[] imsiBuffer; + private byte[] iccidBuffer; public static void install(byte[] bArray, short bOffset, byte bLength) throws ISOException { SampleApplet sampleApplet = new SampleApplet(); @@ -39,6 +49,9 @@ PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0); menuItem2 = toolkitRegistry.initMenuEntry(menuItem2Text, (short) 0, (short) menuItem2Text.length, PRO_CMD_SELECT_ITEM, false, (byte) 0, (short) 0); + + uiccFileView = UICCSystem.getTheUICCView(JCSystem.NOT_A_TRANSIENT_OBJECT); + usimAppFileView = UICCSystem.getTheFileView(usimAID, (short) 0, (byte) usimAID.length, JCSystem.NOT_A_TRANSIENT_OBJECT); } private SampleApplet() { @@ -50,6 +63,8 @@ readBuffer = JCSystem.makeTransientByteArray((short) 256, JCSystem.CLEAR_ON_RESET); imeiBuffer = JCSystem.makeTransientByteArray((short) 15, JCSystem.CLEAR_ON_RESET); + imsiBuffer = JCSystem.makeTransientByteArray((short) (1 + 15), JCSystem.CLEAR_ON_RESET); + iccidBuffer = JCSystem.makeTransientByteArray((short) (1 + 20), JCSystem.CLEAR_ON_RESET); } public Shareable getShareableInterfaceObject(AID clientAID, byte parameter) { @@ -114,6 +129,10 @@ if (selectedItemId == menuItem2) { loadIMEI(); DiagUtil.text(imeiBuffer); + loadIMSI(); + DiagUtil.text(imsiBuffer, (short) 1, (short) imsiBuffer[0]); + loadICCID(true); + DiagUtil.text(iccidBuffer, (short) 1, (short) iccidBuffer[0]); } } @@ -148,4 +167,52 @@ Util.arrayCopyNonAtomic(tmpBuffer, (short) 1, imeiBuffer, (short) 0, (short) 15); } + private void loadICCID(boolean removePadding) throws ToolkitException { + //ICCIDをEF_ICCIDから読み出す + // 参照規格: ETSI TS 102221 Clause 13.2 + short length = 10; + short digits = 20; + readBinaryFromEF(uiccFileView, UICCConstants.FID_EF_ICCID, readBuffer, (short) 0, length); + //文字列に変換 + ByteUtil.nibbleSwap(readBuffer, (short) 0, length); + ByteUtil.bytesToHex(readBuffer, (short) 0, length, iccidBuffer, (short) 1); + + if (removePadding) { + // 末尾が'F'の場合は桁数をつめる + while (iccidBuffer[(short) (digits)] == (byte) 'F') { + digits--; + } + } + iccidBuffer[0] = (byte) digits; + } + + private void loadIMSI() throws ToolkitException { + //IMSI (固定長9バイト)をUSIM ApplicationのEF_IMSIから読み出す + short length = 9; + readBinaryFromEF(usimAppFileView, USIMConstants.FID_EF_IMSI, readBuffer, (short) 0, length); + + // IMSIのデータ形式を考慮して文字列に変換する + length = readBuffer[0]; + if (length == 0 || length > 8) { + imsiBuffer[0] = 0; + return; + } + ByteUtil.nibbleSwap(readBuffer, (short) 1, length); + ByteUtil.bytesToHex(readBuffer, (short) 1, length, tmpBuffer, (short) 0); + + // 末尾が'F'の時は桁数をつめる + // 参照規格: 3GPP TS 31.102 Clause 4.2.2 など + short digits = (short) (length * 2 - 1); + if (tmpBuffer[(short) (digits - 1)] == (byte) 'F') { + digits--; + } + imsiBuffer[0] = (byte) digits; + Util.arrayCopyNonAtomic(tmpBuffer, (short) 1, imsiBuffer, (short) 1, digits); + } + + private short readBinaryFromEF(FileView fileView, short FID, byte[] dstBuffer, short dstOffset, short readLength) { + fileView.select(FID); + return fileView.readBinary((short) 0, dstBuffer, (short) dstOffset, readLength); + } + }
変更箇所では以下の機能を実装しています。
メソッド 機能 readBinaryFromEF() UICC FileView APIを利用してUSIM Applicationに接続し、ファイル (EF, Elementary File )の内容を読み出す loadICCID(), loadIMSI() EFからICCID, IMSIを読み出し、それぞれのデータ形式を考慮して文字列に変換する processToolkit(), processToolkitEvent() スマートフォンのSIM Toolkitアプリ上でメニュー項目 Test
をタップすると、ICCID, IMSIをダイアログに表示する注釈
処理詳細については、ソースコードコメントに記載した参照規格 (ETSI TS 102221, 3GPP TS 31.102など) を参照してください。
上記の変更を行ったら、アプレットがビルドできることを確認してください。 (View → Tool Windows → Antを開き、
Verification
ターゲットを実行する)
HTTPクライアント(アプレットー外部サーバ間のインタフェース方法)¶
SIM自体には通信機能はありませんが、ETSI TS 102223 規格に規定される Bearer Independent Protocol 機能を利用すると、 端末 (スマートフォンや通信モジュール等)がSIMに代わってTCPまたはUDPで外部サーバへ接続して、SIMから外部サーバへの通信を中継させることができます。
以下では、APIの実装例として、HTTPでアプレットコンソールに接続してIMEI・IMSI・ICCIDの各値を送信する機能を追加します。
ソースファイル
SampleApplet.java
を下記からダウンロードしたファイルで上書きします。src/internal/sim/sample_applet/SampleApplet.java
(変更後のファイル)主要な変更内容は以下のとおりです。(詳細についてはソースコードを参照ください)
メソッド 処理内容 openChannel() OPEN CHANNEL コマンドを発行して、Bearer Independent Channel を開く (指定した外部IPアドレスへTCP接続するよう端末に指示する) closeChannel() Bearer Independent Channelを閉じ、端末側のリソースを開放する createHttpHeader(), createJsonBody() HTTP HeaderおよびBody(JSON形式の文字列データ)をRAM上に生成する sendHTTPPost(), sendData() SEND DATAコマンドを発行して、Bearer Independent Channelを使用してTCPのペイロード(HTTP Request)を端末経由で宛先サーバへ送信する initUiccToolkit() アプレットインストール時に、イベントEVENT_EVENT_DOWNLOAD_DATA_AVAILABLE, EVENT_EVENT_DOWNLOAD_CHANNEL_STATUS をprocessToolkit() で受信するための設定を行う processToolkit(), processToolkitEvent(), processHTTPResponse() - イベントEVENT_EVENT_DOWNLOAD_DATA_AVAILABLE を受け取って、RECEIVE DATAコマンドを発行する。
- RECEIVE DATAコマンドの応答から、TCPのペイロード (HTTP Response)を抽出し、先頭部分を ダイアログ表示する。
- 接続先IPアドレスは
SampleApplet.java
の変数 serverAddrにハードコードされていますので、適切な値に書き換えます。 - 開通案内に記されているアプレットコンソールのホスト名 (xxxxx.sim-applet.com , xxxxxは契約毎に異なります) のIPアドレスを調べて書き換えてください。
必要に応じて、
serverPort
の値も変更してください。- 接続先IPアドレスは
上記の変更を行ったら、アプレットをビルドし、
tools/install_applet.bat
を実行してSIMにインストールします。アプレットコンソールにrootアカウントでログインし、アプレットをインストールしたSIMのICCIDを登録します。
このとき、「Send Raw Data」を必ずチェックしてください。 AESKeyは適当な値を入力してください。
注釈
既に同番でSIMが登録済の場合は、削除してから再登録してください。
注釈
本サンプルアプレットはHTTPペイロードを暗号化しないため、
Send Raw Data
を必ずチェックする必要があります。アプレットをインストールしたSIMをスマートフォンに装着して起動し、SIM Toolkitアプリを起動します。
メニュー項目
Test
をタップします。 データの送信(HTTP POSTリクエスト)が成功すると、下図のようにHTTP Responseの先頭部分が表示されます。アプレットコンソール上では、下図のように IMSI, IMEI, Historyの各項目にデータが登録される様子を確認できます。