blob: cb12fb8da713bd98fdd333e17018269dee8b4bc2 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Contributed by Orange */
package android.omapi.cts;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.SystemProperties;
import android.se.omapi.Channel;
import android.se.omapi.Reader;
import android.se.omapi.SEService;
import android.se.omapi.SEService.OnConnectedListener;
import android.se.omapi.Session;
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.PropertyUtil;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
public class OmapiTest {
private final static String UICC_READER_PREFIX = "SIM";
private final static String ESE_READER_PREFIX = "eSE";
private final static String SD_READER_PREFIX = "SD";
private final static String OMAPI_VERSION = "3.3";
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};
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};
private final static byte[] NON_SELECTABLE_AID =
new byte[]{(byte) 0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64,
0x72, 0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, (byte) 0xFF};
/* MANAGE open/close and SELECT AID */
private final static byte[][] ILLEGAL_COMMANDS_TRANSMIT = new byte[][]{{0x00, 0x70, 0x00, 0x00},
{0x00, 0x70, (byte) 0x80, 0x00},
{0x00, (byte) 0xA4, 0x04, 0x04, 0x10, 0x4A, 0x53,
0x52, 0x31, 0x37, 0x37, 0x54, 0x65, 0x73,
0x74, 0x65, 0x72, 0x20, 0x31, 0x2E, 0x30}
};
/* 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}
};
/* 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}
};
/* Case 2 APDU command expects the P2 received in the SELECT command as 1-byte outgoing data */
private static final byte[] CHECK_SELECT_P2_APDU = new byte[] {
0x00, (byte) 0xF4, 0x00, 0x00, 0x00
};
/* OMAPI APDU Test case 1 and 3 */
private final static byte[][] SW_62xx_NO_DATA_APDU =
new byte[][]{{0x00, (byte) 0xF3, 0x00, 0x06},
{0x00, (byte) 0xF3, 0x00, 0x0A, 0x01, (byte) 0xAA}
};
/* OMAPI APDU Test case 2 and 4 */
private final static byte[] SW_62xx_DATA_APDU = new byte[]{0x00, (byte) 0xF3, 0x00, 0x08, 0x00};
private final static byte[] SW_62xx_VALIDATE_DATA_APDU =
new byte[]{0x00, (byte) 0xF3, 0x00, 0x0C, 0x01, (byte) 0xAA, 0x00};
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}
};
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}
};
private final long SERVICE_CONNECTION_TIME_OUT = 3000;
private SEService seService;
private Object serviceMutex = new Object();
private Timer connectionTimer;
private ServiceConnectionTimerTask mTimerTask = new ServiceConnectionTimerTask();
private boolean connected = false;
private final OnConnectedListener mListener = new OnConnectedListener() {
@Override
public void onConnected() {
synchronized (serviceMutex) {
connected = true;
serviceMutex.notify();
}
}
};
class SynchronousExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
private boolean supportsHardware() {
final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
boolean lowRamDevice = SystemProperties.getBoolean("ro.config.low_ram", false);
return !lowRamDevice || pm.hasSystemFeature("android.hardware.type.watch")
|| hasSecureElementPackage(pm);
}
private boolean hasSecureElementPackage(PackageManager pm) {
try {
pm.getPackageInfo("com.android.se", 0 /* flags*/);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
private boolean supportUICCReaders() {
final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC);
}
private boolean supportESEReaders() {
final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE);
}
private boolean supportSDReaders() {
final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD);
}
private boolean supportOMAPIReaders() {
final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
return (pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC)
|| pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE)
|| pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD));
}
private void assertGreaterOrEqual(long greater, long lesser) {
assertTrue("" + greater + " expected to be greater than or equal to " + lesser,
greater >= lesser);
}
@Before
public void setUp() throws Exception {
assumeTrue(PropertyUtil.getFirstApiLevel() > Build.VERSION_CODES.O_MR1);
assumeTrue(supportsHardware());
seService = new SEService(InstrumentationRegistry.getContext(), new SynchronousExecutor(), mListener);
connectionTimer = new Timer();
connectionTimer.schedule(mTimerTask, SERVICE_CONNECTION_TIME_OUT);
}
@After
public void tearDown() throws Exception {
if (seService != null && seService.isConnected()) {
seService.shutdown();
connected = false;
}
}
private void waitForConnection() throws TimeoutException {
synchronized (serviceMutex) {
if (!connected) {
try {
serviceMutex.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!connected) {
throw new TimeoutException(
"Service could not be connected after " + SERVICE_CONNECTION_TIME_OUT
+ " ms");
}
if (connectionTimer != null) {
connectionTimer.cancel();
}
}
}
/** Tests getReaders API */
@Test
public void testGetReaders() {
try {
waitForConnection();
Reader[] readers = seService.getReaders();
ArrayList<Reader> uiccReaders = new ArrayList<Reader>();
ArrayList<Reader> eseReaders = new ArrayList<Reader>();
ArrayList<Reader> sdReaders = new ArrayList<Reader>();
for (Reader reader : readers) {
assertTrue(reader.isSecureElementPresent());
String name = reader.getName();
if (!(name.startsWith(UICC_READER_PREFIX) || name.startsWith(ESE_READER_PREFIX)
|| name.startsWith(SD_READER_PREFIX))) {
fail("Incorrect Reader name");
}
assertNotNull("getseService returned null", reader.getSEService());
if (reader.getName().startsWith(UICC_READER_PREFIX)) {
uiccReaders.add(reader);
}
if (reader.getName().startsWith(ESE_READER_PREFIX)) {
eseReaders.add(reader);
}
if (reader.getName().startsWith(SD_READER_PREFIX)) {
sdReaders.add(reader);
}
}
if (supportUICCReaders()) {
assertGreaterOrEqual(uiccReaders.size(), 1);
// Test API getUiccReader(int slotNumber)
// The result should be the same as getReaders() with UICC reader prefix
for (int i = 1; i <= uiccReaders.size(); i++) {
try {
Reader uiccReader = seService.getUiccReader(i);
if (!uiccReaders.contains(uiccReader))
fail("Incorrect reader object - getUiccReader(" + i + ")");
} catch (IllegalArgumentException e) {
fail("Fail to get Reader object by calling getUiccReader(" + i + ")");
}
}
} else {
assertTrue(uiccReaders.size() == 0);
}
if (supportESEReaders()) {
assertGreaterOrEqual(eseReaders.size(), 1);
} else {
assertTrue(eseReaders.size() == 0);
}
if (supportSDReaders()) {
assertGreaterOrEqual(sdReaders.size(), 1);
} else {
assertTrue(sdReaders.size() == 0);
}
} catch (Exception e) {
fail("Unexpected Exception " + e);
}
}
/** Tests getATR API */
@Test
public void testATR() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
ArrayList<Reader> uiccReaders = new ArrayList<Reader>();
if (readers != null && readers.length > 0) {
for (int i = 0; i < readers.length; i++) {
if (readers[i].getName().startsWith(UICC_READER_PREFIX)) {
uiccReaders.add(readers[i]);
}
}
for (Reader reader : uiccReaders) {
Session session = null;
try {
session = reader.openSession();
} catch (IOException e) {
e.printStackTrace();
}
assertNotNull("Could not open session", session);
byte[] atr = session.getATR();
session.close();
assertNotNull("ATR is Null", atr);
}
}
} catch (Exception e) {
fail("Unexpected Exception " + e);
}
}
/** Tests OpenBasicChannel API when aid is null */
@Test
public void testOpenBasicChannelNullAid() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
Session session = null;
Channel channel = null;
try {
session = reader.openSession();
assertNotNull("Could not open session", session);
channel = session.openBasicChannel(null, (byte) 0x00);
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
if (reader.getName().startsWith(UICC_READER_PREFIX)) {
assertNull("Basic channel on UICC can be opened", channel);
} else {
assertNotNull("Basic Channel cannot be opened", channel);
}
}
} catch (Exception e) {
fail("Unexpected Exception " + e);
}
}
/** Tests OpenBasicChannel API when aid is provided */
@Test
public void testOpenBasicChannelNonNullAid() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
Session session = null;
Channel channel = null;
try {
session = reader.openSession();
assertNotNull("Could not open session", session);
channel = session.openBasicChannel(SELECTABLE_AID, (byte) 0x00);
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
if (reader.getName().startsWith(UICC_READER_PREFIX)) {
assertNull("Basic channel on UICC can be opened", channel);
} else {
assertNotNull("Basic Channel cannot be opened", channel);
}
}
} catch (Exception e) {
fail("Unexpected Exception " + e);
}
}
/** Tests Select API */
@Test
public void testSelectableAid() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
testSelectableAid(reader, SELECTABLE_AID);
}
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
@Test
public void testLongSelectResponse() {
assumeTrue(supportOMAPIReaders());
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);
}
}
private byte[] testSelectableAid(Reader reader, byte[] aid) throws IOException {
byte[] selectResponse = null;
Session session = null;
Channel channel = null;
try {
assertTrue(reader.isSecureElementPresent());
session = reader.openSession();
assertNotNull("Null Session", session);
channel = session.openLogicalChannel(aid, (byte) 0x00);
assertNotNull("Null Channel", channel);
selectResponse = channel.getSelectResponse();
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
assertNotNull("Null Select Response", selectResponse);
assertGreaterOrEqual(selectResponse.length, 2);
assertThat(selectResponse[selectResponse.length - 1] & 0xFF, is(0x00));
assertThat(selectResponse[selectResponse.length - 2] & 0xFF, is(0x90));
return selectResponse;
}
/** Tests if NoSuchElementException in Select */
@Test
public void testWrongAid() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
testNonSelectableAid(reader, NON_SELECTABLE_AID);
}
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
private void testNonSelectableAid(Reader reader, byte[] aid) throws IOException {
Session session = null;
Channel channel = null;
try {
assertTrue(reader.isSecureElementPresent());
session = reader.openSession();
assertNotNull("null session", session);
channel = session.openLogicalChannel(aid, (byte) 0x00);
fail("Exception expected for this test");
} catch (NoSuchElementException e) {
// Catch the expected exception here.
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
}
/** Tests if Security Exception in Transmit */
@Test
public void testSecurityExceptionInTransmit() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
Session session = null;
Channel channel = null;
try {
assertTrue(reader.isSecureElementPresent());
session = reader.openSession();
assertNotNull("null session", session);
channel = session.openLogicalChannel(SELECTABLE_AID, (byte) 0x00);
assertNotNull("Null Channel", channel);
byte[] selectResponse = channel.getSelectResponse();
assertNotNull("Null Select Response", selectResponse);
assertGreaterOrEqual(selectResponse.length, 2);
assertThat(selectResponse[selectResponse.length - 1] & 0xFF, is(0x00));
assertThat(selectResponse[selectResponse.length - 2] & 0xFF, is(0x90));
for (byte[] cmd : ILLEGAL_COMMANDS_TRANSMIT) {
try {
byte[] response = channel.transmit(cmd);
fail("Exception expected for this test");
} catch (SecurityException e) {
// Catch the expected exception here.
}
}
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
}
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
private byte[] internalTransmitApdu(Reader reader, byte[] apdu) throws IOException {
byte[] transmitResponse = null;
Session session = null;
Channel channel = null;
try {
assertTrue(reader.isSecureElementPresent());
session = reader.openSession();
assertNotNull("null session", session);
channel = session.openLogicalChannel(SELECTABLE_AID, (byte) 0x00);
assertNotNull("Null Channel", channel);
byte[] selectResponse = channel.getSelectResponse();
assertNotNull("Null Select Response", selectResponse);
assertGreaterOrEqual(selectResponse.length, 2);
transmitResponse = channel.transmit(apdu);
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
return transmitResponse;
}
private byte[] internalTransmitApduWithoutP2(Reader reader, byte[] apdu,
boolean testLogicalChannel) throws IOException {
byte[] transmitResponse = null;
Session session = null;
Channel channel = null;
try {
assertTrue(reader.isSecureElementPresent());
session = reader.openSession();
assertNotNull("null session", session);
if (testLogicalChannel) {
channel = session.openLogicalChannel(SELECTABLE_AID);
} else {
channel = session.openBasicChannel(SELECTABLE_AID);
}
assertNotNull("Null Channel", channel);
byte[] selectResponse = channel.getSelectResponse();
assertNotNull("Null Select Response", selectResponse);
assertGreaterOrEqual(selectResponse.length, 2);
transmitResponse = channel.transmit(apdu);
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
return transmitResponse;
}
/**
* Tests Transmit API for all readers.
*
* Checks the return status and verifies the size of the
* response.
*/
@Test
public void testTransmitApdu() {
assumeTrue(supportOMAPIReaders());
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);
}
}
/**
* Tests if underlying implementations returns the correct Status Word
*
* TO verify that :
* - the device does not modify the APDU sent to the Secure Element
* - the warning code is properly received by the application layer as SW answer
* - the verify that the application layer can fetch the additionnal data (when present)
*/
@Test
public void testStatusWordTransmit() {
assumeTrue(supportOMAPIReaders());
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);
}
}
/** Test if the responses are segmented by the underlying implementation */
@Test
public void testSegmentedResponseTransmit() {
assumeTrue(supportOMAPIReaders());
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);
}
}
/**
* Tests the P2 value of the select command.
*
* Verifies that the default P2 value (0x00) is not modified by the underlying implementation.
*/
@Test
@ApiTest(apis = {"android.se.omapi.Session#openBasicChannel",
"android.se.omapi.Session#openLogicalChannel"})
public void testP2Value() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
byte[] response = internalTransmitApduWithoutP2(reader, CHECK_SELECT_P2_APDU, true);
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));
// skip basic channel verification for UICC reader
if (reader.getName().startsWith(UICC_READER_PREFIX)) {
continue;
}
response = internalTransmitApduWithoutP2(reader, CHECK_SELECT_P2_APDU, false);
assertGreaterOrEqual(response.length, 3);
assertThat(response[response.length - 1] & 0xFF, is(0x00));
assertThat(response[response.length - 2] & 0xFF, is(0x90));
}
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
/** Tests closeChannels API */
@Test
public void testCloseChannels() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
Session session = null;
Channel channelA = null;
Channel channelB = null;
try {
assertTrue(reader.isSecureElementPresent());
session = reader.openSession();
assertNotNull("null session", session);
channelA = session.openLogicalChannel(SELECTABLE_AID, (byte) 0x00);
assertNotNull("Null Channel", channelA);
channelB = session.openLogicalChannel(LONG_SELECT_RESPONSE_AID, (byte) 0x00);
assertNotNull("Null Channel", channelB);
session.closeChannels();
assertFalse("channel is still open", channelA.isOpen());
assertFalse("channel is still open", channelB.isOpen());
} finally {
if (session != null) session.close();
}
}
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
/** Tests SEService getVersion API */
@Test
@ApiTest(apis = "android.se.omapi.SEService#getVersion")
public void testGetOmapiVersion() {
try {
waitForConnection();
assertTrue("Unexpected OMAPI version", seService.getVersion().equals(OMAPI_VERSION));
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
/** Tests Reader closeSession API */
@Test
@ApiTest(apis = {"android.se.omapi.Reader#closeSessions", "android.se.omapi.Session#isClosed"})
public void testCloseSessions() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
Session sessionA = null;
Session sessionB = null;
try {
assertTrue(reader.isSecureElementPresent());
sessionA = reader.openSession();
assertNotNull("null session", sessionA);
assertFalse("session is closed", sessionA.isClosed());
sessionB = reader.openSession();
assertNotNull("null session", sessionB);
assertFalse("session is closed", sessionB.isClosed());
sessionA.getReader().closeSessions();
assertTrue("session is still open", sessionA.isClosed());
assertTrue("session is still open", sessionB.isClosed());
} finally {
if (sessionA != null) sessionA.close();
if (sessionB != null) sessionB.close();
}
}
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
/** Tests Reader reset API */
@Test
@ApiTest(apis = "android.se.omapi.Reader#reset")
public void testOmapiReaderReset() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
try {
reader.reset();
fail("reset function is not blocked by permission ");
} catch (SecurityException e) {
// Catch the expected exception here
}
}
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
/** Tests Channel getSession API */
@Test
@ApiTest(apis = {"android.se.omapi.Channel#getSession", "android.se.omapi.Session#isClosed",
"android.se.omapi.Channel#isBasicChannel"})
public void testGetSession() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
Session session = null;
Channel channel = null;
try {
session = reader.openSession();
assertNotNull("Could not open session", session);
channel = session.openLogicalChannel(SELECTABLE_AID, (byte) 0x00);
assertFalse("channel is not LogicalChannel", channel.isBasicChannel());
channel.getSession().close();
assertTrue(session.isClosed());
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
}
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
/** Tests Channel selectNext API */
@Test
@ApiTest(apis = "android.se.omapi.Channel#selectNext")
public void testSelectNext() {
assumeTrue(supportOMAPIReaders());
try {
waitForConnection();
Reader[] readers = seService.getReaders();
for (Reader reader : readers) {
Session session = null;
Channel channel = null;
try {
session = reader.openSession();
assertNotNull("Could not open session", session);
channel = session.openLogicalChannel(SELECTABLE_AID, (byte) 0x00);
// no further Applet exists with this AID
assertFalse(channel.selectNext());
// send APDU to check selected applet stays selected on this channel
for (byte[] apdu : NO_DATA_APDU) {
byte[] response = channel.transmit(apdu);
assertThat(response.length, is(2));
assertThat(response[response.length - 1] & 0xFF, is(0x00));
assertThat(response[response.length - 2] & 0xFF, is(0x90));
}
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
}
} catch (Exception e) {
fail("unexpected exception " + e);
}
}
/**
* 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));
}
class ServiceConnectionTimerTask extends TimerTask {
@Override
public void run() {
synchronized (serviceMutex) {
serviceMutex.notifyAll();
}
}
}
}