各種警告系ステータスワードの返却
ターミナルとカードの間で行われるAPDUコマンドのやり取りには、当然ながらカードOSが介在します。ふと見つけてドイツから個人輸入したsysmoUSIM-SJS1 4FFですが、カードOSの制限事項のようなもの、例えばカードOSが通過を許してくれないクラスバイトやインストラクションコードには、これまでのところ出会っていません。OMAPI系テストに関しては、このまますんなり完成させてもらえるような気がしてきました(楽観的)。では、引き続きAndroid Secure Element CTSが期待するJava Cardのアプレットを書いてゆきます。
Omapi.testStatusWordTransmit()が期待すること
テスト側の実装は、下記のようになっています。様々なバリエーションの警告系ステータスワードが期待通りにカードから返ってくることがテストのポイントで、Case 1からCase 4まで全てのパターンのAPDUコマンドでそれを確認したいようです。下記コードはざっくり三つの内部ループで構成されていますが、最初のループでCase 1とCase 3を、次のループでCase 2を、そして最後のループでCase 4をテストするように書かれています。
public void testStatusWordTransmit() { try { waitForConnection(); Reader[] readers = seService.getReaders(); for (Reader reader : readers) { for (byte[] apdu : SW_62xx_NO_DATA_APDU) { for (byte i = 0x00; i < SW_62xx.length; i++) { apdu[2] = (byte)(i+1); byte[] response = internalTransmitApdu(reader, apdu); byte[] SW = SW_62xx[i]; assertThat(response[response.length - 1], is(SW[1])); assertThat(response[response.length - 2], is(SW[0])); } } for (byte i = 0x00; i < SW_62xx.length; i++) { byte[] apdu = SW_62xx_DATA_APDU; apdu[2] = (byte)(i+1); byte[] response = internalTransmitApdu(reader, apdu); byte[] SW = SW_62xx[i]; assertGreaterOrEqual(response.length, 3); assertThat(response[response.length - 1], is(SW[1])); assertThat(response[response.length - 2], is(SW[0])); } for (byte i = 0x00; i < SW_62xx.length; i++) { byte[] apdu = SW_62xx_VALIDATE_DATA_APDU; apdu[2] = (byte)(i+1); byte[] response = internalTransmitApdu(reader, apdu); assertGreaterOrEqual(response.length, apdu.length + 2); byte[] responseSubstring = Arrays.copyOfRange(response, 0, apdu.length); // We should not care about which channel number is actually assigned. responseSubstring[0] = apdu[0]; assertTrue(Arrays.equals(responseSubstring, apdu)); byte[] SW = SW_62xx[i]; assertThat(response[response.length - 1], is(SW[1])); assertThat(response[response.length - 2], is(SW[0])); } } } catch (Exception e) { fail("unexpected exception " + e); } }
Android Secure Element CTSのウェブページでは、下記のように説明(テストに用いるAPDUコマンドや警告系ステータスワードの表はここでは割愛)されているところです。Case 4でカードから受け取るデータの内容については言及がありますが、Case 2については全く説明されていません。Case 4とそっくり同じデータをCase 2のAPDUコマンドに対して返しても問題ないはずですが、Case 2のデータの内容はカード側の実装に依存することになるので、(他の方が作成されたカードもテストできるようにするなら)テスト側では真ん中のループと最後のループを分けざるを得ません。
a. 0xA000000476416E64726F696443545331
iv. The applet should return the following status word responses for the respective Transmit APDU:
...
*The response should contain data that is the same as input APDU, except the first byte is 0x01 instead of 0x00.
本テストで利用されるAPDUコマンドのインストラクションコードは、'F3'。カード側では、P2値を見て期待されている振る舞いを選択する必要があります。
CLA | INS | P1 | P2 | Lc | Data(Lc) | Le | Data(Le) |
---|---|---|---|---|---|---|---|
XX | F3 | YY | 06 | なし | |||
XX | F3 | YY | 0A | 01 | AA | なし | |
XX | F3 | YY | 08 | 00 | あり | ||
XX | F3 | YY | 0C | 01 | AA | 00 | あり |
APDUコマンドのP1の値に応じて、それぞれ特定の警告系ステータスワードがカードから応答されることが期待されています。
P1 specified in APDU | Expected SW | Description |
---|---|---|
01 | 6200 | Warning. State of non-volatile memory is unchanged. |
02 | 6281 | SW2: Part of returned data may be corrupted. |
03 | 6282 | SW2: End of file or record reached before reading Ne bytes. |
04 | 6283 | SW2: Selected file deactivated. |
05 | 6285 | SW2: Selected file in termination state. |
06 | 62F1 | SW2: More data available. |
07 | 62F2 | SW2: More data available and proactive command pending. |
08 | 63F1 | SW2: More data expected. |
09 | 63F2 | SW2: More data expected and proactive command pending. |
0A | 63C2 | SW2: Verification failed, 2 retries remaining. |
0B | 6202 | SW2: Triggering by the card (02). |
0C | 6280 | SW2: Triggering by the card (80). |
0D | 6284 | SW2: File control information not formatted. |
0E | 6286 | SW2: No input data available from a sensor on the card. |
0F | 6300 | Warning. State of non-volatile memory has changed. |
10 | 6381 | SW2: File filled up by the last write. |
これを書いている時点では、上表の下から三つ目のステータスワードは'6286'ではなく、何故か'6282'が再登場していました。Android Secure Element CTSのテスト側の実装を見ると'6286'を期待していましたので、Android Secure Element CTSページの誤記だとわかります。
private final static byte[][] SW_62xx = new byte[][]{{0x62, 0x00}, {0x62, (byte) 0x81}, {0x62, (byte) 0x82}, {0x62, (byte) 0x83}, {0x62, (byte) 0x85}, {0x62, (byte) 0xF1}, {0x62, (byte) 0xF2}, {0x63, (byte) 0xF1}, {0x63, (byte) 0xF2}, {0x63, (byte) 0xC2}, {0x62, 0x02}, {0x62, (byte) 0x80}, {0x62, (byte) 0x84}, {0x62, (byte) 0x86}, {0x63, 0x00}, {0x63, (byte) 0x81} };
2018/12/13 追記 : 上記の誤記は既に訂正され、'6286'が期待されていることをAndroid Secure Element CTSページ上でも確認できます。
このテストもPythonで書きました
こんなに処理を束ねてしまわないで、APDU毎に個別にコードブロックを設けたほうが読みやすかったかもしれません。微妙なラインですが、分解せずに束ねたままにしておきます。
def testStatusWordTransmit(self): selectable_aid = 'A000000476416E64726F696443545331' apdu_list = [ '00F30006', '00F3000A01AA', '00F3000800', '00F3000C01AA00', ] warning_sw_list = [ '6200', '6281', '6282', '6283', '6285', '62F1', '62F2', '63F1', '63F2', '63C2', '6202', '6280', '6284', '6286', '6300', '6381' ] for apdu in apdu_list: for p1 in range(0x10): apdu = apdu[:4] + format(p1 + 1, '02X') + apdu[6:] (response, sw) = self.commandif.send_apdu(selectable_aid, apdu) if sw.upper() != warning_sw_list[p1]: raise RuntimeError('Unexpected warning SW : ' + sw) p2 = apdu[6:8] if p2 in {'06', '0A'}: if len(response) > 0: raise RuntimeError('Unexpected outgoing data : ' + response) elif p2 == '08': if len(response) == 0: raise RuntimeError('Outgoing data is expected') elif p2 == '0C': if apdu[2:] != response[2:len(apdu)].upper(): raise RuntimeError('Outgoing data is different from APDU') else: raise RuntimeError('Program error - P2 : ' + p2)
これをマージしたコミットはこちら。
最初に書いたカード側のコード
まずは、このコミットを作成しました。256バイトを超えるような大きなデータのやり取りはありませんし、簡単に実装してしまおうと考えた次第です。
Case 2コマンドの処理
本テストでターミナル側から受信するAPDUコマンドのLeには、'00'が指定されています。256バイトのデータを返すことにすれば、ステータスワード'6CXX'を使ってデータサイズのネゴシエーションを行なう必要もなく、単純なシーケンスで処理を終えることができます。
case P2_WARNING_SW_CASE2: sendShortData(apdu, buffer[ISO7816.OFFSET_LC]); ISOException.throwIt(WARNING_SWS[p1 - 1]); break; ... private void sendShortData(APDU apdu, short expected) { if (expected == 0x00) { expected = DATA_BUFFER_SIZE; } apdu.setOutgoing(); apdu.setOutgoingLength(expected); apdu.sendBytesLong(sOutgoingData, (short) (DATA_BUFFER_SIZE - expected), expected); }
APDUコマンドのやり取りは下記のようになり、こちらは想定通りです。
C-APDU : 01F3010800 R-APDU : 00000000000000000000000000000000 00000000000000000000000000000000 ... 00000000000000000000000000000000 000000000000000000000000000000FF SW : 6200
Case 4コマンドの処理
こちらのコマンドでは、受け取ったコマンドとほぼ同じものを(クラスバイトだけ'01'に差し替えて)ターミナルに返すことが求められています。APDUクラスのバッファにはコマンドが入っていますので、単純にそれを返せば良いものとして設計されたテストであることが予想されます。APDU.setOutgoingAndSend()を使う簡単な方法を選択し、下記のように書いてみました。
case P2_WARNING_SW_CASE4: if (apdu.setIncomingAndReceive() != 0x01) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } buffer[ISO7816.OFFSET_CLA] = 0x01; apdu.setOutgoingAndSend((short) 0, (short) 7); ISOException.throwIt(WARNING_SWS[p1 - 1]); break;
これを動作させてみたときのコマンドのやり取りが、こちら。シーケンス的には合っていて、テストも通るのですが、ステータスワード'9F07'が気に入らない。APDU.setOutgoingAndSend()を呼んだ後にカードOSが自動的に返してくれているので、この実装のままでは制御しようがありません。
C-APDU : 01F3010C01AA00 R-APDU + SW : 9F07 C-APDU : 01C0000007 R-APDU + SW : 01F3010C01AA006200
他のカードベンダ様のカードならこんな実装は不要なのではないかと思われますが、アプレット側の制御でステータスワード'61XX'を返す処理へと書き換えました。