IntelliJ IDEA利用¶
IntelliJ IDEAを用いて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 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 から、ファイルをダウンロードします。 
- ダウンロードしたzipファイルを、1.で展開したフォルダにコピーします。 
 
 
- 下図のようにJCDKがWindowsのホームパス (フォルダ C:\Users\ユーザ名)に展開されていること、そのフォルダにjava_card_kit-classic-3_0_5-ga-spec-doc-b33-03_jun_2015.zipがあること、の2点を確認してください。
  
 
- 下図のようにJCDKがWindowsのホームパス (フォルダ 
IntelliJ IDEAのインストール¶
- こちら からファイルをダウンロード・インストールします。
- 以降の手順では、IntelliJ IDEA 2023.1 Community Edition を用いて説明します 
- 特別な選択・設定箇所はありません 
 
 
プロジェクトの新規作成¶
プロジェクトの新規作成および文字コード等の設定を行います。
- .editorconfigを編集し、以下の設定を行います。
- Windowsバッチファイルの改行コードはCRLFとし、文字コードは制御なし(unset)にする 
- それ以外のファイルの改行コードはLFとし、文字コードはUTF-8にする 
 
 - root = true [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = false max_line_length = 120 tab_width = 4 [{*.bat,*.BAT}] charset = unset end_of_line = crlf - 注釈 - 本設定を実行しない場合、WindowsバッチファイルをIntelliJ IDEAで編集すると改行コードが変更されてしまうことが原因で動作しなくなります。 
- 必ず設定を行ってください。 
 
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を以下の様に起動します
  
 
- 作成したバッチファイルを下図のように実行します。  
ライブラリの追加¶
開発に必要な3種類のパッケージと関連JavaDocをプロジェクトに追加します。
31130_Annex_A_USIM-API.jarの追加¶
- File > Project Structure > Libraries > 「+」> Java を選択します。
  
 
- ファイル選択画面で、 JarファイルとJavaDocを追加します。
- ここ でダウンロードしたファイルです 
- Jarファイルの選択
- $PROJECT_DIR > sample_applet > lib > 31130-d30 > 31130_Annex_A_USIM-API.jar
 
 
- 選択例)
  
 
- JavaDocの選択
- $PROJECT_DIR > lib > 31130-d30 > 31130_Annex_A_HTML.zip! > 31130_Annex_A_HTML > javadoc
 
 
- 選択例)
  
 
 
 
uicc-api-for-java-card-REL-13_0.jarの追加¶
- 作業要領は上述と同様です。
- Jarファイルの選択
- $PROJECT_DIR > sample_applet > lib > ts_102241v130000p0 > uicc-api-for-java-card-REL-13_0.jar
 
 
- JavaDocの選択
- $PROJECT_DIR > sample_applet > lib > ts_102241v130000p0 > 102241_Annex_A_HTML.zip! > 102241_Annex_A_HTML > javadoc
 
 
 
api_classic-3.0.5の追加¶
- 作業要領は上述と同様です。ファイルは、JCDKをダウンロードした 際のディレクトリ配下にあります。
- Jarファイルの選択
- $HOME > java_card_tools-win-bin-b_17-06_jul_2021 > lib > api_classic-3.0.5.jar
 
 
- JavaDocの選択
- $HOME > java_card_tools-win-bin-b_17-06_jul_2021 > java_card_kit-classic-3_0_5-ga-spec-doc-b33-03_jun_2015.zip! > javacard_specifications-3_0_5-RR\classic
 
 
 
アプレット書き込みツールの設定と機器動作確認¶
アプレットをSIMへインストールするためのツールを設定し、機器動作確認を行います。
- 以下のリンクからバッチファイルを取得して - tools/に保存します。- ファイル - 機能 - SIM毎に異なるSCP03鍵 (OEMKIC, OEMKID, OEMKIK) を記載するファイル - サンプルアプレットのインストールおよび、インストールパラメータの設定 - サンプルアプレットのアンインストール - SIMにインストールされているアプレット等の一覧を表示 - ICカードリーダおよびSIMの接続状況を表示 - GlobalPlatformPro, APDU4JをGitHubからダウンロードする - 環境変数JAVA_HOMEなどを一時的に設定する - 注釈 - 上記のバッチファイルは GlobalPlatformPro (オープンソースソフトウェア)を利用してアプレットのインストール等を行います。 
- Terminalを開き (View → Tool Windows → Terminal), バッチファイル tools/download_tools.batを実行します
- このバッチファイルは - tools/gp.jar,- tools/apdu4j.jarを GitHubからダウンロードします
 
 
- Terminalを開き (View → Tool Windows → Terminal), バッチファイル 
- SIMをICカードリーダに装着し、バッチファイル tools/find_card_readers.batを実行します。
- [*]が表示される行が1行のみ存在することを確認してください。
- [*]が表示されない場合
- ICカードリーダおよびSIMの装着状況を確認してください。 
 
 
- [*]が複数行表示される場合
- 他のSIMやICカード等がPCに装着されていますので 必ず取り外してください 。 
- 誤ってアプレットを書き込むことにより破損する恐れがあります。 
 
 
 
 - Windows PowerShell Copyright (C) Microsoft Corporation. All rights reserved. 新しいクロスプラットフォームの PowerShell をお試しください https://aka.ms/pscore6 PS C:\Users\WDAGUtilityAccount\IntelliJ_IDEA\projects\sample_applet> .\tools\find_card_readers.bat 1: [ ] Microsoft IFD 0 2: [*] SCM Microsystems Inc. SCR33x USB Smart Card Reader 0 PS C:\Users\WDAGUtilityAccount\IntelliJ_IDEA\projects\sample_applet> 
- 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を破損する恐れがあります。 
 
 - PS C:\Users\WDAGUtilityAccount\IntelliJ_IDEA\projects\sample_applet> .\tools\list_applet.bat [WARN] GPSession - GET STATUS failed for 80F28002024F0000 with 0x6985 (Conditions of use not satisfied) DOM: A0000001156000000000000000011001 (PERSONALIZED) Privs: SecurityDomain, TrustedPath, AuthorizedManagement 
- ファイル 
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 
- View → Tool Windows → Ant を開き、「Add Ant build file」を開き、上記で作成した - build.xmlを登録します。
最小構成のアプレットの作成とビルド¶
ここまでの操作でプロジェクト設定が完了したので、 最小構成のアプレットを作成してビルドしてみます。
- ソースファイル - 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」ターゲットを右クリックしてPropertiesを開き(下図)「Make build in background」のチェックを外します。  
- Antウィンドウの「Verification」ターゲットを右クリックして「Run Target」を選択するとビルドが始まります。 
- Verification が正常終了すると下図の状態になります。(Ant build completed with 5 warnings) - ファイル - suncap/sample_applet.capが生成されたことを確認してください。 
アプレットのデバッグ手段の実装¶
SIMは極めて機微な情報を容易に取り出せないように格納するデバイスとして設計されている関係上、アプレットの実機デバッグを行うための機構やデバッガが用意されていません。
以下では、ETSI 102 223 などの規格に規定されている「Card Application Toolkit」の機能を利用して スマートフォンの画面上にメモリダンプ等を表示する処理を実装し、いわゆるprintデバッグが行えるようにします。
- 下記のソースファイルをディレクトリ - src/internal/sim/sample_applet/に追加します。- ソースファイル - 主な実装機能 - byte配列を16進数文字列に変換する処理など - UICC APIを使用してスマートフォン上で動作するSIM Toolkitアプリへ文字列を送り、ダイアログを表示させる 
- 下記のように - src/internal/sim/sample_applet/SampleApplet.javaを変更します。- src/internal/sim/sample_applet/SampleApplet.java(変更後のファイル)diff (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にインストールして、動作確認を行います。
- IntelliJ IDEAのTerminalを起動 (View → Tool Windows → Terminal) し、 バッチファイル - tools\install_applet.batを実行します。- アプレットのインストールが正常完了すると、下記例のように - CAP loaded,- Installed OKが表示されます。- PS C:\Users\takahide\IntelliJ_IDEA\projects\sample_applet> .\tools\install_applet.bat [WARN] GPSession - GET STATUS failed for 80F28002024F0000 with 0x6985 (Conditions of use not satisfied) A0000001157000000000000044455601 deleted. [WARN] GPSession - GET STATUS failed for 80F28002024F0000 with 0x6985 (Conditions of use not satisfied) CAP loaded [WARN] GPSession - GET STATUS failed for 80F28002024F0000 with 0x6985 (Conditions of use not satisfied) 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(変更後のファイル)diff (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(変更後のファイル)diff (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アドレスを調べて書き換えてください。 
 - static byte[] serverAddr = {(byte) 192, (byte) 168, (byte) 0, (byte) 1}; static short serverPort = (short) 80; 
 - 必要に応じて、 - 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の各項目にデータが登録される様子を確認できます。  





 





