Loading core/java/android/security/forensic/ForensicEvent.java +104 −19 Original line number Diff line number Diff line Loading @@ -17,13 +17,17 @@ package android.security.forensic; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.app.admin.ConnectEvent; import android.app.admin.DnsEvent; import android.app.admin.SecurityLog.SecurityEvent; import android.os.Parcel; import android.os.Parcelable; import android.security.Flags; import android.util.ArrayMap; import java.util.Map; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * A class that represents a forensic event. Loading @@ -33,11 +37,24 @@ import java.util.Map; public final class ForensicEvent implements Parcelable { private static final String TAG = "ForensicEvent"; @NonNull private final String mType; public static final int SECURITY_EVENT = 0; public static final int NETWORK_EVENT_DNS = 1; public static final int NETWORK_EVENT_CONNECT = 2; @NonNull private final Map<String, String> mKeyValuePairs; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ ForensicEvent.SECURITY_EVENT, ForensicEvent.NETWORK_EVENT_DNS, ForensicEvent.NETWORK_EVENT_CONNECT, }) public @interface EventType {} @NonNull @EventType private final int mType; private final SecurityEvent mSecurityEvent; private final DnsEvent mNetworkEventDns; private final ConnectEvent mNetworkEventConnect; public static final @NonNull Parcelable.Creator<ForensicEvent> CREATOR = new Parcelable.Creator<>() { Loading @@ -50,30 +67,99 @@ public final class ForensicEvent implements Parcelable { } }; public ForensicEvent(@NonNull String type, @NonNull Map<String, String> keyValuePairs) { mType = type; mKeyValuePairs = keyValuePairs; public ForensicEvent(@NonNull SecurityEvent securityEvent) { mType = SECURITY_EVENT; mSecurityEvent = securityEvent; mNetworkEventDns = null; mNetworkEventConnect = null; } public ForensicEvent(@NonNull DnsEvent dnsEvent) { mType = NETWORK_EVENT_DNS; mNetworkEventDns = dnsEvent; mSecurityEvent = null; mNetworkEventConnect = null; } public ForensicEvent(@NonNull ConnectEvent connectEvent) { mType = NETWORK_EVENT_CONNECT; mNetworkEventConnect = connectEvent; mSecurityEvent = null; mNetworkEventDns = null; } private ForensicEvent(@NonNull Parcel in) { mType = in.readString(); mKeyValuePairs = new ArrayMap<>(in.readInt()); in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class); mType = in.readInt(); switch (mType) { case SECURITY_EVENT: mSecurityEvent = SecurityEvent.CREATOR.createFromParcel(in); mNetworkEventDns = null; mNetworkEventConnect = null; break; case NETWORK_EVENT_DNS: mNetworkEventDns = DnsEvent.CREATOR.createFromParcel(in); mSecurityEvent = null; mNetworkEventConnect = null; break; case NETWORK_EVENT_CONNECT: mNetworkEventConnect = ConnectEvent.CREATOR.createFromParcel(in); mSecurityEvent = null; mNetworkEventDns = null; break; default: throw new IllegalArgumentException("Invalid event type: " + mType); } } public String getType() { /** Returns the type of the forensic event. */ @NonNull public @EventType int getType() { return mType; } public Map<String, String> getKeyValuePairs() { return mKeyValuePairs; /** Returns the SecurityEvent object. */ @NonNull public SecurityEvent getSecurityEvent() { if (mType == SECURITY_EVENT) { return mSecurityEvent; } throw new IllegalArgumentException("Event type is not security event: " + mType); } /** Returns the DnsEvent object. */ @NonNull public DnsEvent getDnsEvent() { if (mType == NETWORK_EVENT_DNS) { return mNetworkEventDns; } throw new IllegalArgumentException("Event type is not network DNS event: " + mType); } /** Returns the ConnectEvent object. */ @NonNull public ConnectEvent getConnectEvent() { if (mType == NETWORK_EVENT_CONNECT) { return mNetworkEventConnect; } throw new IllegalArgumentException("Event type is not network connect event: " + mType); } @Override public void writeToParcel(@NonNull Parcel out, int flags) { out.writeString(mType); out.writeInt(mKeyValuePairs.size()); out.writeMap(mKeyValuePairs); out.writeInt(mType); switch (mType) { case SECURITY_EVENT: out.writeParcelable(mSecurityEvent, flags); break; case NETWORK_EVENT_DNS: out.writeParcelable(mNetworkEventDns, flags); break; case NETWORK_EVENT_CONNECT: out.writeParcelable(mNetworkEventConnect, flags); break; default: throw new IllegalArgumentException("Invalid event type: " + mType); } } @FlaggedApi(Flags.FLAG_AFL_API) Loading @@ -86,7 +172,6 @@ public final class ForensicEvent implements Parcelable { public String toString() { return "ForensicEvent{" + "mType=" + mType + ", mKeyValuePairs=" + mKeyValuePairs + '}'; } } services/core/java/com/android/server/security/forensic/SecurityLogSource.java +1 −44 Original line number Diff line number Diff line Loading @@ -22,9 +22,7 @@ import android.app.admin.DevicePolicyManager; import android.app.admin.SecurityLog.SecurityEvent; import android.content.Context; import android.security.forensic.ForensicEvent; import android.util.ArrayMap; import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; Loading @@ -34,10 +32,6 @@ import java.util.stream.Collectors; public class SecurityLogSource implements DataSource { private static final String TAG = "Forensic SecurityLogSource"; private static final String EVENT_TYPE = "SecurityEvent"; private static final String EVENT_TAG = "TAG"; private static final String EVENT_TIME = "TIME"; private static final String EVENT_DATA = "DATA"; private SecurityEventCallback mEventCallback = new SecurityEventCallback(); private DevicePolicyManager mDpm; Loading Loading @@ -94,46 +88,9 @@ public class SecurityLogSource implements DataSource { List<ForensicEvent> forensicEvents = events.stream() .filter(event -> event != null) .map(event -> toForensicEvent(event)) .map(event -> new ForensicEvent(event)) .collect(Collectors.toList()); mDataAggregator.addBatchData(forensicEvents); } private ForensicEvent toForensicEvent(SecurityEvent event) { ArrayMap<String, String> keyValuePairs = new ArrayMap<>(); keyValuePairs.put(EVENT_TIME, String.valueOf(event.getTimeNanos())); // TODO: Map tag to corresponding string keyValuePairs.put(EVENT_TAG, String.valueOf(event.getTag())); keyValuePairs.put(EVENT_DATA, eventDataToString(event.getData())); return new ForensicEvent(EVENT_TYPE, keyValuePairs); } /** * Convert event data to a String. * * @param obj Object containing an Integer, Long, Float, String, null, or Object[] of the * same. * @return String representation of event data. */ private String eventDataToString(Object obj) { if (obj == null) { return ""; } else if (obj instanceof Integer || obj instanceof Long || obj instanceof Float || obj instanceof String) { return String.valueOf(obj); } else if (obj instanceof Object[]) { Object[] objArray = (Object[]) obj; String[] strArray = new String[objArray.length]; for (int i = 0; i < objArray.length; ++i) { strArray[i] = eventDataToString(objArray[i]); } return Arrays.toString((String[]) strArray); } else { throw new IllegalArgumentException( "Unsupported data type: " + obj.getClass().getSimpleName()); } } } } services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java +18 −23 Original line number Diff line number Diff line Loading @@ -31,6 +31,9 @@ import static org.mockito.Mockito.times; 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.SecurityEvent; import android.content.Context; import android.os.Looper; import android.os.PermissionEnforcer; Loading @@ -40,7 +43,6 @@ import android.os.test.TestLooper; import android.security.forensic.ForensicEvent; import android.security.forensic.IForensicServiceCommandCallback; import android.security.forensic.IForensicServiceStateCallback; import android.util.ArrayMap; import androidx.test.core.app.ApplicationProvider; Loading @@ -53,7 +55,6 @@ import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.List; import java.util.Map; public class ForensicServiceTest { private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN; Loading Loading @@ -300,23 +301,19 @@ public class ForensicServiceTest { ServiceThread mockThread = spy(ServiceThread.class); mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread); String eventOneType = "event_one_type"; String eventOneMapKey = "event_one_map_key"; String eventOneMapVal = "event_one_map_val"; Map<String, String> eventOneMap = new ArrayMap<String, String>(); eventOneMap.put(eventOneMapKey, eventOneMapVal); ForensicEvent eventOne = new ForensicEvent(eventOneType, eventOneMap); SecurityEvent securityEvent = new SecurityEvent(0, new byte[0]); ForensicEvent eventOne = new ForensicEvent(securityEvent); String eventTwoType = "event_two_type"; String eventTwoMapKey = "event_two_map_key"; String eventTwoMapVal = "event_two_map_val"; Map<String, String> eventTwoMap = new ArrayMap<String, String>(); eventTwoMap.put(eventTwoMapKey, eventTwoMapVal); ForensicEvent eventTwo = new ForensicEvent(eventTwoType, eventTwoMap); ConnectEvent connectEvent = new ConnectEvent("127.0.0.1", 80, null, 0); ForensicEvent eventTwo = new ForensicEvent(connectEvent); DnsEvent dnsEvent = new DnsEvent(null, new String[] {"127.0.0.1"}, 1, null, 0); ForensicEvent eventThree = new ForensicEvent(dnsEvent); List<ForensicEvent> events = new ArrayList<>(); events.add(eventOne); events.add(eventTwo); events.add(eventThree); doReturn(true).when(mForensicEventTransportConnection).addData(any()); Loading @@ -327,18 +324,16 @@ public class ForensicServiceTest { ArgumentCaptor<List<ForensicEvent>> captor = ArgumentCaptor.forClass(List.class); verify(mForensicEventTransportConnection).addData(captor.capture()); List<ForensicEvent> receivedEvents = captor.getValue(); assertEquals(receivedEvents.size(), 2); assertEquals(receivedEvents.size(), 3); assertEquals(receivedEvents.getFirst().getType(), eventOneType); assertEquals(receivedEvents.getFirst().getKeyValuePairs().size(), 1); assertEquals(receivedEvents.getFirst().getKeyValuePairs().get(eventOneMapKey), eventOneMapVal); assertEquals(receivedEvents.get(0).getType(), ForensicEvent.SECURITY_EVENT); assertNotNull(receivedEvents.get(0).getSecurityEvent()); assertEquals(receivedEvents.getLast().getType(), eventTwoType); assertEquals(receivedEvents.getLast().getKeyValuePairs().size(), 1); assertEquals(receivedEvents.getLast().getKeyValuePairs().get(eventTwoMapKey), eventTwoMapVal); assertEquals(receivedEvents.get(1).getType(), ForensicEvent.NETWORK_EVENT_CONNECT); assertNotNull(receivedEvents.get(1).getConnectEvent()); assertEquals(receivedEvents.get(2).getType(), ForensicEvent.NETWORK_EVENT_DNS); assertNotNull(receivedEvents.get(2).getDnsEvent()); } private class MockInjector implements ForensicService.Injector { Loading Loading
core/java/android/security/forensic/ForensicEvent.java +104 −19 Original line number Diff line number Diff line Loading @@ -17,13 +17,17 @@ package android.security.forensic; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.app.admin.ConnectEvent; import android.app.admin.DnsEvent; import android.app.admin.SecurityLog.SecurityEvent; import android.os.Parcel; import android.os.Parcelable; import android.security.Flags; import android.util.ArrayMap; import java.util.Map; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * A class that represents a forensic event. Loading @@ -33,11 +37,24 @@ import java.util.Map; public final class ForensicEvent implements Parcelable { private static final String TAG = "ForensicEvent"; @NonNull private final String mType; public static final int SECURITY_EVENT = 0; public static final int NETWORK_EVENT_DNS = 1; public static final int NETWORK_EVENT_CONNECT = 2; @NonNull private final Map<String, String> mKeyValuePairs; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({ ForensicEvent.SECURITY_EVENT, ForensicEvent.NETWORK_EVENT_DNS, ForensicEvent.NETWORK_EVENT_CONNECT, }) public @interface EventType {} @NonNull @EventType private final int mType; private final SecurityEvent mSecurityEvent; private final DnsEvent mNetworkEventDns; private final ConnectEvent mNetworkEventConnect; public static final @NonNull Parcelable.Creator<ForensicEvent> CREATOR = new Parcelable.Creator<>() { Loading @@ -50,30 +67,99 @@ public final class ForensicEvent implements Parcelable { } }; public ForensicEvent(@NonNull String type, @NonNull Map<String, String> keyValuePairs) { mType = type; mKeyValuePairs = keyValuePairs; public ForensicEvent(@NonNull SecurityEvent securityEvent) { mType = SECURITY_EVENT; mSecurityEvent = securityEvent; mNetworkEventDns = null; mNetworkEventConnect = null; } public ForensicEvent(@NonNull DnsEvent dnsEvent) { mType = NETWORK_EVENT_DNS; mNetworkEventDns = dnsEvent; mSecurityEvent = null; mNetworkEventConnect = null; } public ForensicEvent(@NonNull ConnectEvent connectEvent) { mType = NETWORK_EVENT_CONNECT; mNetworkEventConnect = connectEvent; mSecurityEvent = null; mNetworkEventDns = null; } private ForensicEvent(@NonNull Parcel in) { mType = in.readString(); mKeyValuePairs = new ArrayMap<>(in.readInt()); in.readMap(mKeyValuePairs, getClass().getClassLoader(), String.class, String.class); mType = in.readInt(); switch (mType) { case SECURITY_EVENT: mSecurityEvent = SecurityEvent.CREATOR.createFromParcel(in); mNetworkEventDns = null; mNetworkEventConnect = null; break; case NETWORK_EVENT_DNS: mNetworkEventDns = DnsEvent.CREATOR.createFromParcel(in); mSecurityEvent = null; mNetworkEventConnect = null; break; case NETWORK_EVENT_CONNECT: mNetworkEventConnect = ConnectEvent.CREATOR.createFromParcel(in); mSecurityEvent = null; mNetworkEventDns = null; break; default: throw new IllegalArgumentException("Invalid event type: " + mType); } } public String getType() { /** Returns the type of the forensic event. */ @NonNull public @EventType int getType() { return mType; } public Map<String, String> getKeyValuePairs() { return mKeyValuePairs; /** Returns the SecurityEvent object. */ @NonNull public SecurityEvent getSecurityEvent() { if (mType == SECURITY_EVENT) { return mSecurityEvent; } throw new IllegalArgumentException("Event type is not security event: " + mType); } /** Returns the DnsEvent object. */ @NonNull public DnsEvent getDnsEvent() { if (mType == NETWORK_EVENT_DNS) { return mNetworkEventDns; } throw new IllegalArgumentException("Event type is not network DNS event: " + mType); } /** Returns the ConnectEvent object. */ @NonNull public ConnectEvent getConnectEvent() { if (mType == NETWORK_EVENT_CONNECT) { return mNetworkEventConnect; } throw new IllegalArgumentException("Event type is not network connect event: " + mType); } @Override public void writeToParcel(@NonNull Parcel out, int flags) { out.writeString(mType); out.writeInt(mKeyValuePairs.size()); out.writeMap(mKeyValuePairs); out.writeInt(mType); switch (mType) { case SECURITY_EVENT: out.writeParcelable(mSecurityEvent, flags); break; case NETWORK_EVENT_DNS: out.writeParcelable(mNetworkEventDns, flags); break; case NETWORK_EVENT_CONNECT: out.writeParcelable(mNetworkEventConnect, flags); break; default: throw new IllegalArgumentException("Invalid event type: " + mType); } } @FlaggedApi(Flags.FLAG_AFL_API) Loading @@ -86,7 +172,6 @@ public final class ForensicEvent implements Parcelable { public String toString() { return "ForensicEvent{" + "mType=" + mType + ", mKeyValuePairs=" + mKeyValuePairs + '}'; } }
services/core/java/com/android/server/security/forensic/SecurityLogSource.java +1 −44 Original line number Diff line number Diff line Loading @@ -22,9 +22,7 @@ import android.app.admin.DevicePolicyManager; import android.app.admin.SecurityLog.SecurityEvent; import android.content.Context; import android.security.forensic.ForensicEvent; import android.util.ArrayMap; import java.util.Arrays; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; Loading @@ -34,10 +32,6 @@ import java.util.stream.Collectors; public class SecurityLogSource implements DataSource { private static final String TAG = "Forensic SecurityLogSource"; private static final String EVENT_TYPE = "SecurityEvent"; private static final String EVENT_TAG = "TAG"; private static final String EVENT_TIME = "TIME"; private static final String EVENT_DATA = "DATA"; private SecurityEventCallback mEventCallback = new SecurityEventCallback(); private DevicePolicyManager mDpm; Loading Loading @@ -94,46 +88,9 @@ public class SecurityLogSource implements DataSource { List<ForensicEvent> forensicEvents = events.stream() .filter(event -> event != null) .map(event -> toForensicEvent(event)) .map(event -> new ForensicEvent(event)) .collect(Collectors.toList()); mDataAggregator.addBatchData(forensicEvents); } private ForensicEvent toForensicEvent(SecurityEvent event) { ArrayMap<String, String> keyValuePairs = new ArrayMap<>(); keyValuePairs.put(EVENT_TIME, String.valueOf(event.getTimeNanos())); // TODO: Map tag to corresponding string keyValuePairs.put(EVENT_TAG, String.valueOf(event.getTag())); keyValuePairs.put(EVENT_DATA, eventDataToString(event.getData())); return new ForensicEvent(EVENT_TYPE, keyValuePairs); } /** * Convert event data to a String. * * @param obj Object containing an Integer, Long, Float, String, null, or Object[] of the * same. * @return String representation of event data. */ private String eventDataToString(Object obj) { if (obj == null) { return ""; } else if (obj instanceof Integer || obj instanceof Long || obj instanceof Float || obj instanceof String) { return String.valueOf(obj); } else if (obj instanceof Object[]) { Object[] objArray = (Object[]) obj; String[] strArray = new String[objArray.length]; for (int i = 0; i < objArray.length; ++i) { strArray[i] = eventDataToString(objArray[i]); } return Arrays.toString((String[]) strArray); } else { throw new IllegalArgumentException( "Unsupported data type: " + obj.getClass().getSimpleName()); } } } }
services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java +18 −23 Original line number Diff line number Diff line Loading @@ -31,6 +31,9 @@ import static org.mockito.Mockito.times; 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.SecurityEvent; import android.content.Context; import android.os.Looper; import android.os.PermissionEnforcer; Loading @@ -40,7 +43,6 @@ import android.os.test.TestLooper; import android.security.forensic.ForensicEvent; import android.security.forensic.IForensicServiceCommandCallback; import android.security.forensic.IForensicServiceStateCallback; import android.util.ArrayMap; import androidx.test.core.app.ApplicationProvider; Loading @@ -53,7 +55,6 @@ import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.List; import java.util.Map; public class ForensicServiceTest { private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN; Loading Loading @@ -300,23 +301,19 @@ public class ForensicServiceTest { ServiceThread mockThread = spy(ServiceThread.class); mDataAggregator.setHandler(mLooperOfDataAggregator, mockThread); String eventOneType = "event_one_type"; String eventOneMapKey = "event_one_map_key"; String eventOneMapVal = "event_one_map_val"; Map<String, String> eventOneMap = new ArrayMap<String, String>(); eventOneMap.put(eventOneMapKey, eventOneMapVal); ForensicEvent eventOne = new ForensicEvent(eventOneType, eventOneMap); SecurityEvent securityEvent = new SecurityEvent(0, new byte[0]); ForensicEvent eventOne = new ForensicEvent(securityEvent); String eventTwoType = "event_two_type"; String eventTwoMapKey = "event_two_map_key"; String eventTwoMapVal = "event_two_map_val"; Map<String, String> eventTwoMap = new ArrayMap<String, String>(); eventTwoMap.put(eventTwoMapKey, eventTwoMapVal); ForensicEvent eventTwo = new ForensicEvent(eventTwoType, eventTwoMap); ConnectEvent connectEvent = new ConnectEvent("127.0.0.1", 80, null, 0); ForensicEvent eventTwo = new ForensicEvent(connectEvent); DnsEvent dnsEvent = new DnsEvent(null, new String[] {"127.0.0.1"}, 1, null, 0); ForensicEvent eventThree = new ForensicEvent(dnsEvent); List<ForensicEvent> events = new ArrayList<>(); events.add(eventOne); events.add(eventTwo); events.add(eventThree); doReturn(true).when(mForensicEventTransportConnection).addData(any()); Loading @@ -327,18 +324,16 @@ public class ForensicServiceTest { ArgumentCaptor<List<ForensicEvent>> captor = ArgumentCaptor.forClass(List.class); verify(mForensicEventTransportConnection).addData(captor.capture()); List<ForensicEvent> receivedEvents = captor.getValue(); assertEquals(receivedEvents.size(), 2); assertEquals(receivedEvents.size(), 3); assertEquals(receivedEvents.getFirst().getType(), eventOneType); assertEquals(receivedEvents.getFirst().getKeyValuePairs().size(), 1); assertEquals(receivedEvents.getFirst().getKeyValuePairs().get(eventOneMapKey), eventOneMapVal); assertEquals(receivedEvents.get(0).getType(), ForensicEvent.SECURITY_EVENT); assertNotNull(receivedEvents.get(0).getSecurityEvent()); assertEquals(receivedEvents.getLast().getType(), eventTwoType); assertEquals(receivedEvents.getLast().getKeyValuePairs().size(), 1); assertEquals(receivedEvents.getLast().getKeyValuePairs().get(eventTwoMapKey), eventTwoMapVal); assertEquals(receivedEvents.get(1).getType(), ForensicEvent.NETWORK_EVENT_CONNECT); assertNotNull(receivedEvents.get(1).getConnectEvent()); assertEquals(receivedEvents.get(2).getType(), ForensicEvent.NETWORK_EVENT_DNS); assertNotNull(receivedEvents.get(2).getDnsEvent()); } private class MockInjector implements ForensicService.Injector { Loading