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

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

OmapiTest.testLongSelectResponse()のサポート

OSMOCOM (Open Source Mobile Communications) コミュニティのSIMカードsysmoUSIM-SJS1 4FF上で動くAndroid Secure Element CTSJava Cardアプレットを作成するこのシリーズ、前回までにOmapiTest.testTransmitApdu()の対応を終え、今回はOmapiTest.testLongSelectResponse()をサポートするための作業です。

cheerio-the-bear.hatenablog.com

testLongSelectResponse()が期待していること

OmapiTest.javaは、こちらで参照可能です。このテストケースでは、AID 'A000000476416E64726F696443545332'のアプレットを選択し、その応答として返ってくるデータがBET-TLVフォーマットになっていることを確認します。

    private final static byte[] LONG_SELECT_RESPONSE_AID =
            new byte[]{(byte) 0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
                    0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x32};
...
    @Test
    public void testLongSelectResponse() {
        try {
            waitForConnection();
            Reader[] readers = seService.getReaders();
            for (Reader reader : readers) {
                byte[] selectResponse = testSelectableAid(reader, LONG_SELECT_RESPONSE_AID);
                assertTrue("Select Response is not complete", verifyBerTlvData(selectResponse));
            }
        } catch (Exception e) {
            fail("unexpected exception " + e);
        }
    }

Android Secure Element CTSのページを見てみると、上記の定数名が示しているように"long"である必要もないと書かれています。

b. A000000476416E64726F696443545332

i. When selected, this AID should return a select response greater than 2 bytes that are correctly formatted using Basic Encoding Rules (BER) and tag-length-value (TLV).

とは言え、ここはひとつGoogleさんのサンプルコードと同じ、長めでそれらしい構造のデータを拝借して返しておくのが無難です。

BER-TLVフォーマットを確認するverifyBerTlvData()の実装が一見雑に見えますが、セキュアエレメントからとても長い応答データが返ってくることを想定しているわけではないので、この程度で事が足りるということなんでしょう。

    /**
     * Verifies TLV data
     * @param tlv
     * @return true if the data is tlv formatted, false otherwise
     */
    private static boolean verifyBerTlvData(byte[] tlv){
        if (tlv == null || tlv.length == 0) {
            throw new RuntimeException("Invalid tlv, null");
        }
        int i = 0;
        if ((tlv[i++] & 0x1F) == 0x1F) {
            // extra byte for TAG field
            i++;
        }
        int len = tlv[i++] & 0xFF;
        if (len > 127) {
            // more than 1 byte for length
            int bytesLength = len-128;
            len = 0;
            for(int j = bytesLength; j > 0; j--) {
                len += (len << 8) + (tlv[i++] & 0xFF);
            }
        }
        // Additional 2 bytes for the SW
        return (tlv.length == (i+len+2));
    }

ふたつ目のアプレットの追加

これまでcapファイルに含まれるモジュールはひとつ、よってMakefileに記述するモジュールAIDもひとつでした。そこで、ふたつ目のモジュールのためのAID(下記APPLET_AID_32)を追加し、その実装には同じOmapiApplet.javaを共用させます。SELECTされたときに指定されているAIDを確認することで、同じアプレット実装にAIDに応じた異なる振る舞いをさせることが可能です。

APPLET_AID_31       = 0xA0:0x00:0x00:0x04:0x76:0x41:0x6E:0x64:0x72:0x6F:0x69:0x64:0x43:0x54:0x53:0x31
APPLET_AID_32       = 0xA0:0x00:0x00:0x04:0x76:0x41:0x6E:0x64:0x72:0x6F:0x69:0x64:0x43:0x54:0x53:0x32
APPLET_NAME = com.github.cheeriotb.cts.cardlet.OmapiApplet
...
    $(JAVA) -jar $(JAVACARD_SDK_DIR)/bin/converter.jar    \
        -d $(BUILD_JAVACARD_DIR)                          \
        -classdir $(BUILD_CLASSES_DIR)                    \
        -exportpath $(JAVACARD_EXPORT_DIR)                \
        -applet $(APPLET_AID_31) $(APPLET_NAME)           \
        -applet $(APPLET_AID_32) $(APPLET_NAME)           \
        $(PACKAGE_NAME) $(PACKAGE_AID) $(PACKAGE_VERSION)

これでビルドしたアプレットをロード・インストールした上で、GET STATUSコマンドで確認してみました。期待通りにAIDがふたつ見えています。

$ python shadysim.py --pcsc --list-applets --kic 9A665E9CDA096DAE9C04894785EB0B18 --kid 1A8DD88431450CAF8D3719F6380F0A18
...
AID: a0000000090001ffffffff8900, State: 01, Privs: 00
        Instance AID: a0000000090001ffffffff8900000000
        Instance AID: a0000000090001ffffffff89b00010
AID: a0000000871002ff49ffff8900, State: 01, Privs: 00
        Instance AID: a0000000871002ff49ffff89040b0000
AID: ff434e52581040040203, State: 01, Privs: 00
        Instance AID: ff434e52581040040203000000000000
AID: a00000047600, State: 01, Privs: 00
        Instance AID: a000000476416e64726f696443545331
        Instance AID: a000000476416e64726f696443545332

アプレットそのものに追加するコードは、シンプルなものです。そのコミットはこちら。

github.com

アプレットのテスト

Pythonスクリプトにも、OmapiTest.testLongSelectResponse()同等のテストケースを追加しました。

github.com

実行した結果がこちら。AID 32に対するSELECTコマンドに、6Fタグで始まるBET-TLVフォーマットのFCIテンプレートを期待通りに返しています。

$ python sects.py
...
started: testLongSelectResponse
C-APDU : 0070000001
R-APDU + SW : 019000
C-APDU : 01A4040010A000000476416E64726F69644354533200
R-APDU + SW : 9f8a
C-APDU : 01c000008a
R-APDU + SW : 6f81878410536c6374526573705465737420312e30a5736506072a864886fc6b01600b06092a864886fc6b020202630906072a864886fc6b03640b06092a864886fc6b048000640b06092a864886fc6b040255640b06092a864886fc6b040370650d060b2a864886fc6b0504020000660c060a2b060104012a026e01039f6e060077602201209f6501ff9000
C-APDU : 00708001
R-APDU + SW : 9000
finished: testLongSelectResponse

Pythonは、見よう見まねでもそれらしく書けてしまうのが、逆に良くなかったりするんじゃないかと思います。ちゃんと学習すれば、もっと綺麗なコードになるんでしょう。時間が取れれば、何か評判の良い書籍でも探しますか..