Case 2コマンドやCase 4コマンドへの長いデータの応答
Android Secure Element CTSに使えるSIMカードをsysmoUSIM-SJS1 4FFで作ってみる件、次はOmapiTest.testSegmentedResponseTransmit()です。
testSegmentedResponseTransmit()が期待する動作
テストコードによると、セキュアエレメントから返ってくるデータのサイズがAPDUコマンドのP1/P2部分と一致すること、また返ってきたデータの末尾が0xFFであることが期待されています。
/** Test if the responses are segmented by the underlying implementation */ @Test public void testSegmentedResponseTransmit() { try { waitForConnection(); Reader[] readers = seService.getReaders(); for (Reader reader : readers) { for (byte[] apdu : SEGMENTED_RESP_APDU) { byte[] response = internalTransmitApdu(reader, apdu); byte[] b = { 0x00, 0x00, apdu[2], apdu[3] }; ByteBuffer wrapped = ByteBuffer.wrap(b); int expectedLength = wrapped.getInt(); assertThat(response.length, is(expectedLength + 2)); assertThat(response[response.length - 1] & 0xFF, is(0x00)); assertThat(response[response.length - 2] & 0xFF, is(0x90)); assertThat(response[response.length - 3] & 0xFF, is(0xFF)); } } } catch (Exception e) { fail("unexpected exception " + e); } }
Android Secure Element CTSサイトでは、下記のように説明されているところです。データのやり取りが送受信ともにあるCase 4のコマンドがふたつ、その他はCase 2コマンドということになります。
v. The applet should return segmented responses with 0xFF as the last data byte and have the respective status words and response lengths for the following APDUs.
- 0x00C2080000
- 0x00C4080002123400
- 0x00C6080000
- 0x00C8080002123400
- 0x00C27FFF00
- 0x00CF080000
- 0x94C2080000
ただ、テストコードのコメントを確認すると、もうひとつ「セグメントのサイズ」についても期待があることがわかります。インストラクションコード0xC2および0xC4、0xCFのコマンドでは、ひとつのAPDUコマンドに対してカードから応答されるデータの最大サイズが255バイト(+ステータスワード)であること、またその他のインストラクションコードにおいては256バイトであることが期待されています。テストでは実際にはチェックされないところですが、期待に沿えるようにします。
private final static byte[][] SEGMENTED_RESP_APDU = new byte[][]{ //Get response Case2 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes {0x00, (byte) 0xC2, 0x08, 0x00, 0x00}, //Get response Case4 61FF+61XX with answer length (P1P2) of 0x0800, 2048 bytes {0x00, (byte) 0xC4, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00}, //Get response Case2 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes {0x00, (byte) 0xC6, 0x08, 0x00, 0x00}, //Get response Case4 6100+61XX with answer length (P1P2) of 0x0800, 2048 bytes {0x00, (byte) 0xC8, 0x08, 0x00, 0x02, 0x12, 0x34, 0x00}, //Test device buffer capacity 7FFF data {0x00, (byte) 0xC2, (byte) 0x7F, (byte) 0xFF, 0x00}, //Get response 6CFF+61XX with answer length (P1P2) of 0x0800, 2048 bytes {0x00, (byte) 0xCF, 0x08, 0x00, 0x00}, //Get response with another CLA with answer length (P1P2) of 0x0800, 2048 bytes {(byte) 0x94, (byte) 0xC2, 0x08, 0x00, 0x00} };
Case 4コマンド(T=0)
順番は前後しますが、データの送受信がともに発生するCase 4コマンドから順におさらいします。今回のテストケースに登場するふたつ目のコマンド、0x00C4080002123400がアプリケーションレイヤのCase 4コマンド(C-APDU)で、下記のように構成されています。
CLA | INS | P1 | P2 | Lc | Data(Lc) | Le |
---|---|---|---|---|---|---|
XX | C4 | 08 | 00 | 02 | 1234 | 00 |
トランスポートレイヤでは、見え方がちょっと変わります。トランスポートレイヤでカードに最初に届けられるのは、最初の5バイトです。それを受け取ったカードがインストラクションコードをターミナル側に返すと、ターミナル側からカード側に送りたかったデータ(ここでは0x1234)を受け取ることができます。その逆、カード側からターミナル側に送信したいデータは、このコマンドのやり取りの中には登場しません。最後にステータスワードとして61 YY(このSW2は次のコマンドでカード側が送信可能なデータのサイズ)を返すことで、次にターミナルがGET RESPONSE (0xC0)コマンドを使ってそのデータを取りにくることを期待します。
Terminal | Card | |
---|---|---|
XX C4 08 00 02 | → | |
← | INS (C4) | |
Data (1234) | → | |
← | SW (61 YY) |
Java Cardアプレットを実装する上では、細かいところを気にしなくてもそれなりに動いてくれるようになっています。ADPU.setIncomingAndReceive()をコールするだけで、インストラクションコードの送信とデータの受信をフレームワークが実行してくれるようです。前回までのCase 4コマンド処理は下記のようになっていましたが、ステータスワード61 YYを送信する処理を書いていませんでした。送信できるデータをフレームワークに預けておくだけで、自動的にステータスワード61 YYを送信し、次に受信しているはずのGET RESPONSE (0xC0)コマンドも知らないところでハンドリングしてくれるようです。
case INS_BASIC_CASE_4: ... if (apdu.setIncomingAndReceive() != 0x01) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } apdu.setOutgoing(); apdu.setOutgoingLength((short) sResponseBuffer.length); apdu.sendBytesLong(sResponseBuffer, (short) 0, (short) sResponseBuffer.length); break;
フレームワークに自動的に処理させず、作成中のアプレットに可能な限りコマンドを処理させたいので、GET RESPONSE (0xC0)コマンドがアプレットに届くよう、ステータスワード61 YYを自前で返すように書き換えます。
Case 2コマンド(T=0)
今回のテストケースの最初のコマンド0x00C2080000は、Case 2のAPDUコマンド(C-APDU)です。Case 2の場合にはデータのやり取りは一方向、カード側からターミナル側への送信のみになります。
CLA | INS | P1 | P2 | Le |
---|---|---|---|---|
0xXX | 0xC2 | 0x08 | 0x00 | 0x00 |
ターミナル側が期待している受信データのサイズ(Le)よりも、実際にカードが提供できるデータのサイズのほうが少ない場合には、ステータスワード6C YYを応答し、Leだけ書き直した同じコマンドがターミナル側から再送されるのを待ちます。
Terminal | Card | |
---|---|---|
XX C2 08 00 00 | → | |
← | SW (6C YY) | |
XX C2 08 00 YY | → | |
← | INS (C2) Data (..) SW (90 00) |
両者のデータサイズの認識が一致している場合、もしくは実際にカードが提供できるデータサイズのほうが多い場合には、このコマンドのやり取りの中でデータの送受信まで完結します。
Terminal | Card | |
---|---|---|
XX C2 08 00 00 | → | |
← | INS (C2) Data (..) SW (90 00) |
前回までのCase 2コマンド処理が、こちら。上記の判断をフレームワークにお任せするコードにしていましたが、こちらもアプレットの判断で制御するように書き換えます。
case INS_BASIC_CASE_2: ... apdu.setOutgoing(); apdu.setOutgoingLength((short) sResponseBuffer.length); apdu.sendBytesLong(sResponseBuffer, (short) 0, (short) sResponseBuffer.length); break;
テストコードの追加
OmapiTest.testSegmentedResponseTransmit()をPythonのコードで再現したものが、こちらです。
ターミナル側で受信したデータの長さが期待通りであること(APDUコマンドのP1/P2と一致すること)を確認し、その最終バイトが0xFFで、ステータスワードが正常終了を示していることを確認します。Pythonのスライスにも慣れてきたような気がします。
def testSegmentedResponseTransmit(self): ... for apdu in segmented_response_apdu_list: (response, sw) = self.commandif.send_apdu(selectable_aid, apdu) # P1 + P2 indicates the expected length of the output data. if len(response) != (int(apdu[4:8], 16) * 2): raise RuntimeError('Unexpected length of data is received : ' + str(len(response))) # The last data byte shall be 0xFF though the other bytes are not cared at all. if int(response[-2:], 16) != 0xFF: raise RuntimeError('Unexpected byte is received : ' + response[-2:]) if sw != '9000': raise RuntimeError('SW is not 9000 : ' + sw)