クマは森で用を足しますか?

アウトプットは重要です。

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()を再現してみました。

github.com

なんでしょうこの感じ。Pythonで書くのって、なんか楽しいですね。書いた後にビルドしたりロードしたりしないでいいクイックさが、この好感を生んでいるんでしょうか。三項演算子なんてこんな書き方になっちゃって、なんだかかっこいい。

            secure = False if (cla & 0x0C) != 0 else True

入門書とか読まなくてもそれなりに書けちゃう言語仕様なので、逆に迷います。ちゃんと学習したほうが良いコードを書けるようになるんでしょうけど、なんとかやれている間はどうしても億劫になります。

では、続けてJava Cardアプレット側のコードを書いてみようと思います。うまく書けると良いのですが。