論理チャネルを開くときにP2値を割愛するケース
Android Secure Element CTSページのOpen Mobile API関連の記述について言えば、これまでの対応でカバーできていないのは、おそらく下記の件のみではないでしょうか。
a. 0xA000000476416E64726F696443545331
...
vi. The applet should return success status word 0x9000 for the given APDU: 0x00F40000
これだけを見れば、APDUコマンド'00F40000'に対してステータスワード'9000'を返せば良いだけのようですが、そんなことはありません。この誤記は、いずれ更新されるのではないでしょうか(と信じたい)。
2018/12/13 追記 : 下記のように訂正されていました。この下の説明文が要らなくなりましたね ..
a. 0xA000000476416E64726F696443545331
...
vi.The applet should return the value of P2 received in the SELECT command + the success status word (i.e 0x009000) for the given APDU: 0x00F4000000
OmapiTest.testP2Value()が期待すること
テスト側の実装は下記の通りで、ステータスワード'9000'の前にカードから'00'を受け取りたいことがわかります。
private final static byte[] CHECK_SELECT_P2_APDU = new byte[]{0x00, (byte) 0xF4, 0x00, 0x00}; ... /** Test the P2 value of the select command sent by the underlying implementation */ @Test public void testP2Value() { try { waitForConnection(); Reader[] readers = seService.getReaders(); for (Reader reader : readers) { byte[] response = internalTransmitApdu(reader, CHECK_SELECT_P2_APDU); assertGreaterOrEqual(response.length, 3); assertThat(response[response.length - 1] & 0xFF, is(0x00)); assertThat(response[response.length - 2] & 0xFF, is(0x90)); assertThat(response[response.length - 3] & 0xFF, is(0x00)); } } catch (Exception e) { fail("unexpected exception " + e); } }
このAPDUコマンドを送信する際に論理チャネルをひとつオープンしますが、そのときのSELECTコマンドで指定されるP2値が'00'であることを確認するテストケースであるものと解釈できます。Open Mobile APIのSession.openLogicalChannel()ではP2値の指定を割愛することができますが、その際には自動的にP2値として'00'が補われることを確認するものです。
/** * This method is provided to ease the development of mobile application and for compliancy * with existing applications. * This method is equivalent to openLogicalChannel(aid, P2=0x00) ... */ public @Nullable Channel openLogicalChannel(@Nullable byte[] aid) throws IOException { return openLogicalChannel(aid, (byte) 0x00); }
OmapiTest.testP2Value()をPythonで書き直す
単純なテストだなと思ってこれだけ書いたら、うまく動いてくれませんでした。
def testP2Value(self): print('started: ' + sys._getframe().f_code.co_name) selectable_aid = 'A000000476416E64726F696443545331' apdu = '00F40000' (response, sw) = self.commandif.send_apdu(selectable_aid, apdu) if response != '00': raise RuntimeError('Unexpected outgoing data : ' + response) if sw != '9000': raise RuntimeError('Unexpected status word : ' + sw) print('finished: ' + sys._getframe().f_code.co_name)
確かによく見ると、このテストで送信するAPDUコマンドがちょっと変です。カードからのデータ受信を期待するCase 2のAPDUコマンドなのに、最後のLeを省略してCase 1のようなAPDUコマンド'00F40000'を送信することになっています。ターミナル側で最後の'00'が補われるので、カード側から見れば何も違いはないということなのでしょうが。
Terminal | Card | |
---|---|---|
CLA INS P1 P2 P3=00 | → | |
← | SW (XX YY) |
従って、下記のようにTPDUレベルでは末尾の'00'が補われたコマンド'XXF4000000'が送信され、ステータスワード'6C01'がカードから返ってくるのが期待されているコマンドシーケンスになります。
Terminal | Card | |
---|---|---|
XX F4 00 00 00 | → | |
← | SW (6C 01) |
ステータスワード'6CXX'が返ってくるときには、送信したAPDUコマンドの末尾にはLeが付いていることを想定していましたので、単純にそれを適切なデータサイズへと差し替える処理にしていました。
if sw1 == 0x6C: (response, sw) = self.transport.send_apdu(apdu[:-2] + sw[2:4])
本テストで送信するAPDUコマンドにはLeがありませんので、その場合には適切なデータサイズを末尾にLeとして付加するように変更する必要があったというわけです。(正直どうでもいいこと)
if sw1 == 0x6C: if len(apdu) > 8: apdu = apdu[:-2] + sw[2:4] else: apdu = apdu[:8] + sw[2:4] (response, sw) = self.transport.send_apdu(apdu)
今回のコミットがこちら。
2018/12/13 追記 : 最新のテストコードでは、'00F40000' ではなく '00F4000000' を送信するように修正されています。上述の話も、今となっては要らなくなっています。
アプレット側もサクサク実装
ほんの数秒で書いたものが動いてくれているので、本日のところはこれで良いことにしましょう。
case INS_CHECK_P2: if (p1 != 0x00 || p2 != 0x00) { ISOException.throwIt(ISO7816.SW_WRONG_P1P2); } if (buffer[ISO7816.OFFSET_LC] == 0x01) { buffer[0] = mSelectP2; apdu.setOutgoingAndSend((short) 0, (short) 1); } else { // Return SW 6C01 if Le is bigger than the the actual outgoing data. ISOException.throwIt((short) (ISO7816.SW_CORRECT_LENGTH_00 + 0x01)); } break;
期待通りに1バイトだけ要求されていれば、SELECTコマンドを受け取ったときに退避してあったP2値を返します。要求バイト数が1以外であれば、ステータスワード'6C01'を返します。アップロードしたコミットに仕込んだtypoは、また後日にでも修正することにします。
テストを走らせてみると、このようなコマンドシーケンスになります。
started: testP2Value C-APDU : 0070000001 R-APDU + SW : 019000 C-APDU : 01A4040010A000000476416E64726F69644354533100 R-APDU + SW : 9F0C C-APDU : 01C000000C R-APDU + SW : 6F0a640353010162038501019000 C-APDU : 01F40000 R-APDU + SW : 6C01 C-APDU : 01F4000001 R-APDU + SW : 009000 C-APDU : 00708001 R-APDU + SW : 9000 finished: testP2Value