OmapiTest.testTransmitApdu()が期待するSIMカードの振る舞い(1)
sysmoUSIM-SJS1 4FFを、Android Secure Element CTSの要求仕様に沿ったSIMカードへと仕立て上げることができますかどうか。ビルド済みオブジェクトとして提供されているgoogle-cardlet.capを、そのまま単純にロードすることはできませんでした。では、順番にひとつずつ検証してゆこうと思います。
cheerio-the-bear.hatenablog.com
OMAPIテスト
Android Secure Element CTSを構成するソースファイルのひとつOmapiTest.javaは、Android 9.0 Pieから標準APIに加えられたOpen Mobile API (OMAPI)をテストするためのものです。Android端末内の実装をテストするためのテストケースも含まれていますので、UICC SEに特定の振る舞いを期待するテストケースはと言うと、きっとこのくらい。
- OmapiTest.java
- testLongSelectResponse()
- testTransmitApdu()
- testStatusWordTransmit()
- testSegmentedResponseTransmit()
- testP2Value()
いわゆる一般的なAPDUコマンドをやり取りしてそうな、testTransmitApdu()から確認してゆきます。
OmapiTest.testTransmitApdu()の実装
AID 'A000000476416E64726F696443545331'に対して2つのリストに登録されているAPDUコマンドを順に送信し、そのレスポンスを評価するテストになっています。
private final static byte[] SELECTABLE_AID = new byte[]{(byte) 0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x31}; ... /** * Tests Transmit API for all readers. * * Checks the return status and verifies the size of the * response. */ @Test public void testTransmitApdu() { try { waitForConnection(); Reader[] readers = seService.getReaders(); for (Reader reader : readers) { for (byte[] apdu : NO_DATA_APDU) { byte[] response = internalTransmitApdu(reader, apdu); assertThat(response.length, is(2)); assertThat(response[response.length - 1] & 0xFF, is(0x00)); assertThat(response[response.length - 2] & 0xFF, is(0x90)); } for (byte[] apdu : DATA_APDU) { byte[] response = internalTransmitApdu(reader, apdu); /* 256 byte data and 2 bytes of status word */ assertThat(response.length, is(258)); assertThat(response[response.length - 1] & 0xFF, is(0x00)); assertThat(response[response.length - 2] & 0xFF, is(0x90)); } } } catch (Exception e) { fail("unexpected exception " + e); } }
NO_DATA_APDUにはCase 1コマンドとCase 3コマンドが
NO_DATA_APDUのほうはこんな感じ。前半はCase 1コマンドで送受信ともデータなし、後半は僅か1バイトだけをセキュアエレメントに向けて送信するCase 3コマンドとして想定されています。あまり美しくないので、いつか機会があれば整形し直したい感じです。
/* OMAPI APDU Test case 1 and 3 */ private final static byte[][] NO_DATA_APDU = new byte[][]{{0x00, 0x06, 0x00, 0x00}, {(byte) 0x80, 0x06, 0x00, 0x00}, {(byte) 0xA0, 0x06, 0x00, 0x00}, {(byte) 0x94, 0x06, 0x00, 0x00}, {0x00, 0x0A, 0x00, 0x00, 0x01, (byte) 0xAA}, {(byte) 0x80, 0x0A, 0x00, 0x00, 0x01, (byte) 0xAA}, {(byte) 0xA0, 0x0A, 0x00, 0x00, 0x01, (byte) 0xAA}, {(byte) 0x94, 0x0A, 0x00, 0x00, 0x01, (byte) 0xAA} };
Android Secure Element CTSの説明文から、該当する部分を引用します。
a. 0xA000000476416E64726F696443545331 ii.The applet should return no data when it receives the following APDUs in Transmit: i.0x00060000 ii.0x80060000 iii.0xA0060000 iv.0x94060000 v.0x000A000001AA vi.0x800A000001AA vii.0xA00A000001AA viii.0x940A000001AA
DATA_APDUにはCase 2コマンドとCase 4コマンドが
一方、DATA_APDUのほうはこんなリストになっています。
/* OMAPI APDU Test case 2 and 4 */ private final static byte[][] DATA_APDU = new byte[][]{{0x00, 0x08, 0x00, 0x00, 0x00}, {(byte) 0x80, 0x08, 0x00, 0x00, 0x00}, {(byte) 0xA0, 0x08, 0x00, 0x00, 0x00}, {(byte) 0x94, 0x08, 0x00, 0x00, 0x00}, {0x00, (byte) 0x0C, 0x00, 0x00, 0x01, (byte) 0xAA, 0x00}, {(byte) 0x80, (byte) 0x0C, 0x00, 0x00, 0x01, (byte) 0xAA, 0x00}, {(byte) 0xA0, (byte) 0x0C, 0x00, 0x00, 0x01, (byte) 0xAA, 0x00}, {(byte) 0x94, (byte) 0x0C, 0x00, 0x00, 0x01, (byte) 0xAA, 0x00} };
データの内容はさておき、セキュアエレメント側から256バイトのアウトプットがあり、正常終了していさえすれば良いようです。
a. 0xA000000476416E64726F696443545331 iii. The applet should return 256-byte data for the following Transmit APDUs: i.0x0008000000 ii.0x8008000000 iii.0xA008000000 iv.0x9408000000 v.0x000C000001AA00 vi.0x800C000001AA00 vii.0xA00C000001AA00 viii.0x940C000001AA00
OmapiTest.testTransmitApdu()をPythonスクリプトで再現
アプレットを作ってテストする際に、Android Secure Element CTSをいちいち実行するのは面倒です。あまり積極的に触れてはこなかったPythonの勉強にもなるかなと思い、PythonのスクリプトでOmapiTest.testTransmitApdu()を再現してみました。
なんでしょうこの感じ。Pythonで書くのって、なんか楽しいですね。書いた後にビルドしたりロードしたりしないでいいクイックさが、この好感を生んでいるんでしょうか。三項演算子なんてこんな書き方になっちゃって、なんだかかっこいい。
secure = False if (cla & 0x0C) != 0 else True
入門書とか読まなくてもそれなりに書けちゃう言語仕様なので、逆に迷います。ちゃんと学習したほうが良いコードを書けるようになるんでしょうけど、なんとかやれている間はどうしても億劫になります。