blob: 5998ceaac0bc290ee87a8f7b8260a3cac2a2a354 [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.accesscontrol2.cts;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
import static org.junit.Assume.assumeTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import android.content.pm.PackageManager;
import android.os.RemoteException;
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 android.support.test.InstrumentationRegistry;
import android.os.Build;
import com.android.compatibility.common.util.PropertyUtil;
public class AccessControlTest {
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 byte[] AID_40 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x40 };
private final static byte[] AID_41 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x41 };
private final static byte[] AID_42 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x42 };
private final static byte[] AID_43 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x43 };
private final static byte[] AID_44 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x44 };
private final static byte[] AID_45 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x45 };
private final static byte[] AID_46 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x46 };
private final static byte[] AID_47 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x47 };
private final static byte[] AID_48 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x48 };
private final static byte[] AID_49 = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, 0x49 };
private final static byte[] AID_4A = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, (byte) 0x4A };
private final static byte[] AID_4B = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, (byte) 0x4B };
private final static byte[] AID_4C = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, (byte) 0x4C };
private final static byte[] AID_4D = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, (byte) 0x4D };
private final static byte[] AID_4E = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, (byte) 0x4E };
private final static byte[] AID_4F = new byte[] { (byte) 0xA0, 0x00, 0x00,
0x04, 0x76, 0x41, 0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64, 0x43, 0x54,
0x53, (byte) 0x4F };
private final static byte[][] AUTHORIZED_AID = new byte[][] { AID_40,
AID_41, AID_43, AID_45, AID_46 };
private final static byte[][] UNAUTHORIZED_AID = new byte[][] { AID_42,
AID_44, AID_47, AID_48, AID_49, AID_4A, AID_4B, AID_4C, AID_4D, AID_4E, AID_4F};
/* Authorized APDU for AID_40 */
private final static byte[][] AUTHORIZED_APDU_AID_40 = new byte[][] {
{ 0x00, 0x06, 0x00, 0x00 }, { (byte) 0xA0, 0x06, 0x00, 0x00 },};
/* Unauthorized APDU for AID_40 */
private final static byte[][] UNAUTHORIZED_APDU_AID_40 = new byte[][] {
{ 0x00, 0x08, 0x00, 0x00, 0x00 },
{ (byte) 0x80, 0x06, 0x00, 0x00 },
{ (byte) 0xA0, 0x08, 0x00, 0x00, 0x00 },
{ (byte) 0x94, 0x06, 0x00, 0x00, 0x00 }, };
/* Authorized APDU for AID_41 */
private final static byte[][] AUTHORIZED_APDU_AID_41 = new byte[][] {
{ (byte) 0x94, 0x06, 0x00, 0x00 },
{ (byte) 0x94, 0x08, 0x00, 0x00, 0x00 },
{ (byte) 0x94, (byte) 0x0C, 0x00, 0x00, 0x01, (byte) 0xAA, 0x00 },
{ (byte) 0x94, 0x0A, 0x00, 0x00, 0x01, (byte) 0xAA } };
/* Unauthorized APDU for AID_41 */
private final static byte[][] UNAUTHORIZED_APDU_AID_41 = new byte[][] {
{ 0x00, 0x06, 0x00, 0x00 }, { (byte) 0x80, 0x06, 0x00, 0x00 },
{ (byte) 0xA0, 0x06, 0x00, 0x00 },
{ 0x00, 0x08, 0x00, 0x00, 0x00 },
{ (byte) 0x00, 0x0A, 0x00, 0x00, 0x01, (byte) 0xAA },
{ (byte) 0x80, 0x0A, 0x00, 0x00, 0x01, (byte) 0xAA },
{ (byte) 0xA0, 0x0A, 0x00, 0x00, 0x01, (byte) 0xAA },
{ (byte) 0x80, 0x08, 0x00, 0x00, 0x00 },
{ (byte) 0xA0, 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 },
};
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() {
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 = PropertyUtil.propertyEquals("ro.config.low_ram", "true");
return !lowRamDevice || (lowRamDevice && pm.hasSystemFeature("android.hardware.type.watch"));
}
@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();
}
}
}
@Test
public void testAuthorizedAID() {
for (byte[] aid : AUTHORIZED_AID) {
testSelectableAid(aid);
}
}
@Test
public void testUnauthorizedAID() {
for (byte[] aid : UNAUTHORIZED_AID) {
testUnauthorisedAid(aid);
}
}
@Test
public void testAuthorizedAPDUAID40() {
for (byte[] apdu : AUTHORIZED_APDU_AID_40) {
testTransmitAPDU(AID_40, apdu);
}
}
@Test
public void testUnauthorisedAPDUAID40() {
for (byte[] apdu : UNAUTHORIZED_APDU_AID_40) {
testUnauthorisedAPDU(AID_40, apdu);
}
}
@Test
public void testAuthorizedAPDUAID41() {
for (byte[] apdu : AUTHORIZED_APDU_AID_41) {
testTransmitAPDU(AID_41, apdu);
}
}
@Test
public void testUnauthorisedAPDUAID41() {
for (byte[] apdu : UNAUTHORIZED_APDU_AID_41) {
testUnauthorisedAPDU(AID_41, apdu);
}
}
private void testSelectableAid(byte[] aid) {
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(aid, (byte) 0x00);
assertNotNull("Null Channel", channel);
byte[] selectResponse = channel.getSelectResponse();
assertNotNull("Null Select Response", selectResponse);
assertThat(selectResponse[selectResponse.length - 1] & 0xFF, is(0x00));
assertThat(selectResponse[selectResponse.length - 2] & 0xFF, is(0x90));
assertTrue("Select Response is not complete", verifyBerTlvData(selectResponse));
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
}
} catch (Exception e) {
fail("Unexpected Exception " + e);
}
}
private void testUnauthorisedAid(byte[] aid) {
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);
try {
channel = session.openLogicalChannel(aid, (byte) 0x00);
fail("SecurityException Expected");
} catch (SecurityException e) {
// Expected
}
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
}
} catch (Exception e) {
fail("Unexpected Exception " + e);
}
}
private void testTransmitAPDU(byte[] aid, byte[] apdu) {
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(aid, (byte) 0x00);
assertNotNull("Null Channel", channel);
byte[] selectResponse = channel.getSelectResponse();
assertNotNull("Null Select Response", selectResponse);
assertThat(selectResponse[selectResponse.length - 1] & 0xFF, is(0x00));
assertThat(selectResponse[selectResponse.length - 2] & 0xFF, is(0x90));
assertTrue("Select Response is not complete", verifyBerTlvData(selectResponse));
byte[] apduResponse = channel.transmit(apdu);
assertNotNull("Null Channel", apduResponse);
} finally {
if (channel != null) channel.close();
if (session != null) session.close();
}
}
} catch (Exception e) {
fail("Unexpected Exception " + e);
}
}
private void testUnauthorisedAPDU(byte[] aid, byte[] apdu) {
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(aid, (byte) 0x00);
assertNotNull("Null Channel", channel);
byte[] selectResponse = channel.getSelectResponse();
assertNotNull("Null Select Response", selectResponse);
assertThat(selectResponse[selectResponse.length - 1] & 0xFF, is(0x00));
assertThat(selectResponse[selectResponse.length - 2] & 0xFF, is(0x90));
assertTrue("Select Response is not complete", verifyBerTlvData(selectResponse));
try {
channel.transmit(apdu);
fail("Security Exception is expected");
} catch (SecurityException e) {
// Expected
}
} 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;
byte[] key = new byte[2];
key[0] = tlv[i];
if ((key[0] & 0x1F) == 0x1F) {
// extra byte for TAG field
key[1] = tlv[i = i + 1];
}
int len = tlv[i = i + 1] & 0xFF;
if (len > 127) {
// more than 1 byte for length
int bytesLength = len - 128;
len = 0;
for (int j = bytesLength - 1; j >= 0; j--) {
len += (tlv[i = i + 1] & 0xFF) * Math.pow(10, j);
}
}
return tlv.length == (i + len + 3);
}
class ServiceConnectionTimerTask extends TimerTask {
@Override
public void run() {
synchronized (serviceMutex) {
serviceMutex.notifyAll();
}
}
}
}