Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8ecb3688 authored by Liz Prucka's avatar Liz Prucka Committed by Android (Google) Code Review
Browse files

Merge "[IntrusionDetction] Add internal security event callback" into main

parents a29cd592 59db40c6
Loading
Loading
Loading
Loading
+4 −4
Original line number Diff line number Diff line
@@ -19,10 +19,10 @@ package android.security.intrusiondetection;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.app.admin.ConnectEvent;
import android.app.admin.DnsEvent;
import android.app.admin.SecurityLog.SecurityEvent;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.security.Flags;
@@ -223,13 +223,13 @@ public final class IntrusionDetectionEvent implements Parcelable {
        out.writeInt(mType);
        switch (mType) {
            case SECURITY_EVENT:
                out.writeParcelable(mSecurityEvent, flags);
                mSecurityEvent.writeToParcel(out, flags);
                break;
            case NETWORK_EVENT_DNS:
                out.writeParcelable(mNetworkEventDns, flags);
                mNetworkEventDns.writeToParcel(out, flags);
                break;
            case NETWORK_EVENT_CONNECT:
                out.writeParcelable(mNetworkEventConnect, flags);
                mNetworkEventConnect.writeToParcel(out, flags);
                break;
            default:
                throw new IllegalArgumentException("Invalid event type: " + mType);
+1 −1
Original line number Diff line number Diff line
@@ -62,7 +62,7 @@ public class DataAggregator {
    /** Initialize DataSources */
    private void initialize() {
        mDataSources.add(new SecurityLogSource(mContext, this));
        mDataSources.add(new NetworkLogSource(mContext, this));
        mDataSources.add(new NetworkLogSource(this));
    }

    /**
+1 −3
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.server.security.intrusiondetection;

import android.app.admin.ConnectEvent;
import android.app.admin.DnsEvent;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.net.IIpConnectivityMetrics;
import android.net.INetdEventCallback;
@@ -44,8 +43,7 @@ public class NetworkLogSource implements DataSource {
    private IIpConnectivityMetrics mIpConnectivityMetrics;
    private long mId;

    public NetworkLogSource(Context context, DataAggregator dataAggregator)
            throws SecurityException {
    public NetworkLogSource(DataAggregator dataAggregator) throws SecurityException {
        mDataAggregator = dataAggregator;
        mPm = LocalServices.getService(PackageManagerInternal.class);
        mId = 0;
+9 −6
Original line number Diff line number Diff line
@@ -19,14 +19,15 @@ package com.android.server.security.intrusiondetection;
import android.Manifest.permission;
import android.annotation.RequiresPermission;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.Context;
import android.security.intrusiondetection.IntrusionDetectionEvent;
import android.util.Slog;

import com.android.server.LocalServices;

import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.stream.Collectors;

@@ -36,13 +37,13 @@ public class SecurityLogSource implements DataSource {

    private SecurityEventCallback mEventCallback;
    private DevicePolicyManager mDpm;
    private Executor mExecutor;
    private DevicePolicyManagerInternal mDpmInternal;
    private DataAggregator mDataAggregator;

    public SecurityLogSource(Context context, DataAggregator dataAggregator) {
        mDataAggregator = dataAggregator;
        mDpm = context.getSystemService(DevicePolicyManager.class);
        mExecutor = Executors.newSingleThreadExecutor();
        mDpmInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
        mEventCallback = new SecurityEventCallback();
    }

@@ -50,12 +51,13 @@ public class SecurityLogSource implements DataSource {
    @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
    public void enable() {
        enableAuditLog();
        mDpm.setAuditLogEventCallback(mExecutor, mEventCallback);
        mDpmInternal.setInternalEventsCallback(mEventCallback);
    }

    @Override
    @RequiresPermission(permission.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
    public void disable() {
        mDpmInternal.setInternalEventsCallback(null);
        disableAuditLog();
    }

@@ -82,10 +84,11 @@ public class SecurityLogSource implements DataSource {

        @Override
        public void accept(List<SecurityEvent> events) {
            if (events.size() == 0) {
            if (events == null || events.size() == 0) {
                Slog.w(TAG, "No events received; caller may not be authorized");
                return;
            }

            List<IntrusionDetectionEvent> intrusionDetectionEvents =
                    events.stream()
                            .filter(event -> event != null)
+15 −163
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@
package com.android.server.security.intrusiondetection;

import static android.Manifest.permission.BIND_INTRUSION_DETECTION_EVENT_TRANSPORT_SERVICE;
import static android.Manifest.permission.INTERNET;
import static android.Manifest.permission.MANAGE_INTRUSION_DETECTION_STATE;
import static android.Manifest.permission.READ_INTRUSION_DETECTION_STATE;

@@ -28,7 +27,6 @@ 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;
@@ -37,8 +35,8 @@ import static org.mockito.Mockito.verify;

import android.annotation.SuppressLint;
import android.app.admin.ConnectEvent;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DnsEvent;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.ComponentName;
import android.content.Context;
@@ -53,37 +51,22 @@ 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 android.util.Log;

import androidx.test.core.app.ApplicationProvider;

import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.multiuser.annotations.RequireRunOnSystemUser;
import com.android.bedstead.nene.TestApis;
import com.android.bedstead.nene.devicepolicy.DeviceOwner;
import com.android.bedstead.permissions.CommonPermissions;
import com.android.bedstead.permissions.PermissionContext;
import com.android.bedstead.permissions.annotations.EnsureHasPermission;
import com.android.coretests.apps.testapp.LocalIntrusionDetectionEventTransport;
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;
@@ -107,7 +90,6 @@ 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;
@@ -124,6 +106,8 @@ public class IntrusionDetectionServiceTest {
        "com.android.coretests.apps.testapp";
    private static final String TEST_SERVICE = TEST_PKG + ".TestLoggingService";

    DevicePolicyManagerInternal mDevicePolicyManagerInternal;

    @SuppressLint("VisibleForTests")
    @Before
    public void setUp() throws Exception {
@@ -189,6 +173,7 @@ public class IntrusionDetectionServiceTest {
    }

    @Test
    @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
    public void testAddStateCallback_Disabled_TwoStateCallbacks() throws RemoteException {
        StateCallback scb1 = new StateCallback();
        assertEquals(STATE_UNKNOWN, scb1.mState);
@@ -204,7 +189,7 @@ public class IntrusionDetectionServiceTest {
    }

    @Test
    @Ignore("Unit test does not run as system service UID")
    @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
    public void testRemoveStateCallback() throws RemoteException {
        mIntrusionDetectionService.setState(STATE_DISABLED);
        StateCallback scb1 = new StateCallback();
@@ -220,15 +205,19 @@ public class IntrusionDetectionServiceTest {
        mIntrusionDetectionService.getBinderService().removeStateCallback(scb2);

        CommandCallback ccb = new CommandCallback();

        // Enable will fail; caller does not run as system server.
        doNothing().when(mDataAggregator).enable();
        mIntrusionDetectionService.getBinderService().enable(ccb);

        mTestLooper.dispatchAll();
        assertEquals(STATE_ENABLED, scb1.mState);
        assertEquals(STATE_DISABLED, scb2.mState);
        assertNull(ccb.mErrorCode);
    }

    @Ignore("Unit test does not run as system service UID")
    @Test
    @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
    public void testEnable_FromDisabled_TwoStateCallbacks() throws RemoteException {
        mIntrusionDetectionService.setState(STATE_DISABLED);
        StateCallback scb1 = new StateCallback();
@@ -243,6 +232,9 @@ public class IntrusionDetectionServiceTest {

        CommandCallback ccb = new CommandCallback();
        mIntrusionDetectionService.getBinderService().enable(ccb);

        // Enable will fail; caller does not run as system server.
        doNothing().when(mDataAggregator).enable();
        mTestLooper.dispatchAll();

        verify(mDataAggregator, times(1)).enable();
@@ -319,7 +311,7 @@ public class IntrusionDetectionServiceTest {
        assertNull(ccb.mErrorCode);
    }

    @Ignore("Enable once the IntrusionDetectionEventTransportConnection is ready")
    @EnsureHasPermission(CommonPermissions.MANAGE_DEVICE_POLICY_AUDIT_LOGGING)
    @Test
    public void testEnable_FromDisable_TwoStateCallbacks_TransportUnavailable()
            throws RemoteException {
@@ -389,146 +381,6 @@ public class IntrusionDetectionServiceTest {
        assertNotNull(receivedEvents.get(2).getDnsEvent());
    }

    @Test
    @Ignore("Unit test does not run as system service UID")
    @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);

        // 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
    @Ignore("Unit test does not run as system service UID")
    @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);

        // 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();
            }
        }
    }

    @Test
    @RequireRunOnSystemUser
    @EnsureHasPermission(