summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/tests/security/intrusiondetection/AndroidManifest.xml13
-rw-r--r--services/tests/security/intrusiondetection/res/xml/device_admin.xml18
-rw-r--r--services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java224
3 files changed, 252 insertions, 3 deletions
diff --git a/services/tests/security/intrusiondetection/AndroidManifest.xml b/services/tests/security/intrusiondetection/AndroidManifest.xml
index f388e7ea8590..39e41cddb662 100644
--- a/services/tests/security/intrusiondetection/AndroidManifest.xml
+++ b/services/tests/security/intrusiondetection/AndroidManifest.xml
@@ -18,10 +18,19 @@
package="com.android.server.security.intrusiondetection.tests">
<uses-permission android:name="android.permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING" />
- <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+ <uses-permission android:name="android.permission.INTERNET"/>
- <application android:testOnly="true">
+ <application android:testOnly="true" android:debuggable="true" android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner"/>
+ <receiver android:name="com.android.server.security.intrusiondetection.IntrusionDetectionAdminReceiver"
+ android:permission="android.permission.BIND_DEVICE_ADMIN"
+ android:exported="true">
+ <meta-data android:name="android.app.device_admin"
+ android:resource="@xml/device_admin"/>
+ <intent-filter>
+ <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+ </intent-filter>
+ </receiver>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/services/tests/security/intrusiondetection/res/xml/device_admin.xml b/services/tests/security/intrusiondetection/res/xml/device_admin.xml
new file mode 100644
index 000000000000..f8cd8f0b9b44
--- /dev/null
+++ b/services/tests/security/intrusiondetection/res/xml/device_admin.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2024 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.
+-->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
+</device-admin>
diff --git a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
index bc854cf6488b..c185ad5ffd61 100644
--- a/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
+++ b/services/tests/security/intrusiondetection/src/com/android/server/security/intrusiondetection/IntrusionDetectionServiceTest.java
@@ -16,14 +16,19 @@
package com.android.server.security.intrusiondetection;
+import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -33,7 +38,9 @@ import static org.mockito.Mockito.verify;
import android.annotation.SuppressLint;
import android.app.admin.ConnectEvent;
import android.app.admin.DnsEvent;
+import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Looper;
import android.os.PermissionEnforcer;
@@ -43,19 +50,44 @@ import android.os.test.TestLooper;
import android.security.intrusiondetection.IIntrusionDetectionServiceCommandCallback;
import android.security.intrusiondetection.IIntrusionDetectionServiceStateCallback;
import android.security.intrusiondetection.IntrusionDetectionEvent;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
import androidx.test.core.app.ApplicationProvider;
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.annotations.AfterClass;
+import com.android.bedstead.harrier.annotations.BeforeClass;
+import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.devicepolicy.DeviceOwner;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.permissions.CommonPermissions;
+import com.android.bedstead.permissions.PermissionContext;
+import com.android.bedstead.permissions.annotations.EnsureHasPermission;
import com.android.server.ServiceThread;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
+import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+@RunWith(BedsteadJUnit4.class)
public class IntrusionDetectionServiceTest {
private static final int STATE_UNKNOWN =
IIntrusionDetectionServiceStateCallback.State.UNKNOWN;
@@ -73,6 +105,8 @@ public class IntrusionDetectionServiceTest {
private static final int ERROR_DATA_SOURCE_UNAVAILABLE =
IIntrusionDetectionServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE;
+ private static DeviceOwner sDeviceOwner;
+
private Context mContext;
private IntrusionDetectionEventTransportConnection mIntrusionDetectionEventTransportConnection;
private DataAggregator mDataAggregator;
@@ -83,6 +117,28 @@ public class IntrusionDetectionServiceTest {
private Looper mLooperOfDataAggregator;
private FakePermissionEnforcer mPermissionEnforcer;
+ @BeforeClass
+ public static void setDeviceOwner() {
+ ComponentName admin =
+ new ComponentName(
+ ApplicationProvider.getApplicationContext(),
+ IntrusionDetectionAdminReceiver.class);
+ try {
+ sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
+ } catch (NeneException e) {
+ fail("Failed to set device owner " + admin.flattenToString() + ": " + e);
+ }
+ }
+
+ @AfterClass
+ public static void removeDeviceOwner() {
+ try {
+ sDeviceOwner.remove();
+ } catch (NeneException e) {
+ fail("Failed to remove device owner : " + e);
+ }
+ }
+
@SuppressLint("VisibleForTests")
@Before
public void setUp() {
@@ -343,6 +399,172 @@ public class IntrusionDetectionServiceTest {
assertNotNull(receivedEvents.get(2).getDnsEvent());
}
+ @Test
+ @RequireRunOnSystemUser
+ public void testDataSources_Initialize_HasDeviceOwner() throws Exception {
+ NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
+ SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
+
+ assertTrue(networkLogSource.initialize());
+ assertTrue(securityLogSource.initialize());
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ public void testDataSources_Initialize_NoDeviceOwner() throws Exception {
+ NetworkLogSource networkLogSource = new NetworkLogSource(mContext, mDataAggregator);
+ SecurityLogSource securityLogSource = new SecurityLogSource(mContext, mDataAggregator);
+ ComponentName admin = sDeviceOwner.componentName();
+
+ try {
+ sDeviceOwner.remove();
+ assertFalse(networkLogSource.initialize());
+ assertFalse(securityLogSource.initialize());
+ } finally {
+ sDeviceOwner = TestApis.devicePolicy().setDeviceOwner(admin);
+ }
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void testDataAggregator_AddSecurityEvent() throws Exception {
+ mIntrusionDetectionService.setState(STATE_ENABLED);
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+ assertTrue(mDataAggregator.initialize());
+
+ // SecurityLogging generates a number of events and callbacks, so create a latch to wait for
+ // the given event.
+ String eventString = this.getClass().getName() + ".testSecurityEvent";
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
+ doAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock input) {
+ List<IntrusionDetectionEvent> receivedEvents =
+ (List<IntrusionDetectionEvent>) input.getArguments()[0];
+ for (IntrusionDetectionEvent event : receivedEvents) {
+ if (event.getType() == IntrusionDetectionEvent.SECURITY_EVENT) {
+ SecurityEvent securityEvent = event.getSecurityEvent();
+ Object[] eventData = (Object[]) securityEvent.getData();
+ if (securityEvent.getTag() == SecurityLog.TAG_KEY_GENERATED
+ && eventData[1].equals(eventString)) {
+ latch.countDown();
+ }
+ }
+ }
+ return true;
+ }
+ })
+ .when(mIntrusionDetectionEventTransportConnection).addData(any());
+ mDataAggregator.enable();
+
+ // Generate the security event.
+ generateSecurityEvent(eventString);
+ TestApis.devicePolicy().forceSecurityLogs();
+
+ // Verify the event is received.
+ mTestLooper.startAutoDispatch();
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ mTestLooper.stopAutoDispatch();
+
+ mDataAggregator.disable();
+ }
+
+ @Test
+ @RequireRunOnSystemUser
+ @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
+ public void testDataAggregator_AddNetworkEvent() throws Exception {
+ mIntrusionDetectionService.setState(STATE_ENABLED);
+ ServiceThread mockThread = spy(ServiceThread.class);
+ mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread);
+ assertTrue(mDataAggregator.initialize());
+
+ // Network logging may log multiple and callbacks, so create a latch to wait for
+ // the given event.
+ // eventServer must be a valid domain to generate a network log event.
+ String eventServer = "google.com";
+ final CountDownLatch latch = new CountDownLatch(1);
+ // TODO: Replace this mock when the IntrusionDetectionEventTransportConnection is ready.
+ doAnswer(
+ new Answer<Boolean>() {
+ @Override
+ public Boolean answer(InvocationOnMock input) {
+ List<IntrusionDetectionEvent> receivedEvents =
+ (List<IntrusionDetectionEvent>) input.getArguments()[0];
+ for (IntrusionDetectionEvent event : receivedEvents) {
+ if (event.getType()
+ == IntrusionDetectionEvent.NETWORK_EVENT_DNS) {
+ DnsEvent dnsEvent = event.getDnsEvent();
+ if (dnsEvent.getHostname().equals(eventServer)) {
+ latch.countDown();
+ }
+ }
+ }
+ return true;
+ }
+ })
+ .when(mIntrusionDetectionEventTransportConnection).addData(any());
+ mDataAggregator.enable();
+
+ // Generate the network event.
+ generateNetworkEvent(eventServer);
+ TestApis.devicePolicy().forceNetworkLogs();
+
+ // Verify the event is received.
+ mTestLooper.startAutoDispatch();
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ mTestLooper.stopAutoDispatch();
+
+ mDataAggregator.disable();
+ }
+
+ /** Emits a given string into security log (if enabled). */
+ private void generateSecurityEvent(String eventString)
+ throws IllegalArgumentException, GeneralSecurityException, IOException {
+ if (eventString == null || eventString.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Error generating security event: eventString must not be empty");
+ }
+
+ final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
+ keyGen.initialize(
+ new KeyGenParameterSpec.Builder(eventString, KeyProperties.PURPOSE_SIGN).build());
+ // Emit key generation event.
+ final KeyPair keyPair = keyGen.generateKeyPair();
+ assertNotNull(keyPair);
+
+ final KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
+ ks.load(null);
+ // Emit key destruction event.
+ ks.deleteEntry(eventString);
+ }
+
+ /** Emits a given string into network log (if enabled). */
+ private void generateNetworkEvent(String server) throws IllegalArgumentException, IOException {
+ if (server == null || server.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Error generating network event: server must not be empty");
+ }
+
+ HttpURLConnection urlConnection = null;
+ int connectionTimeoutMS = 2_000;
+ try (PermissionContext p = TestApis.permissions().withPermission(INTERNET)) {
+ final URL url = new URL("http://" + server);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setConnectTimeout(connectionTimeoutMS);
+ urlConnection.setReadTimeout(connectionTimeoutMS);
+ urlConnection.getResponseCode();
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
+ }
+
private class MockInjector implements IntrusionDetectionService.Injector {
private final Context mContext;