/* * Copyright (C) 2016 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. */ package com.android.cts.deviceandprofileowner; import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT; import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.SecurityLog.LEVEL_ERROR; import static android.app.admin.SecurityLog.LEVEL_INFO; import static android.app.admin.SecurityLog.LEVEL_WARNING; import static android.app.admin.SecurityLog.TAG_ADB_SHELL_CMD; import static android.app.admin.SecurityLog.TAG_ADB_SHELL_INTERACTIVE; import static android.app.admin.SecurityLog.TAG_APP_PROCESS_START; import static android.app.admin.SecurityLog.TAG_CAMERA_POLICY_SET; import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_INSTALLED; import static android.app.admin.SecurityLog.TAG_CERT_AUTHORITY_REMOVED; import static android.app.admin.SecurityLog.TAG_CERT_VALIDATION_FAILURE; import static android.app.admin.SecurityLog.TAG_CRYPTO_SELF_TEST_COMPLETED; import static android.app.admin.SecurityLog.TAG_KEYGUARD_DISABLED_FEATURES_SET; import static android.app.admin.SecurityLog.TAG_KEYGUARD_DISMISSED; import static android.app.admin.SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT; import static android.app.admin.SecurityLog.TAG_KEYGUARD_SECURED; import static android.app.admin.SecurityLog.TAG_KEY_DESTRUCTION; import static android.app.admin.SecurityLog.TAG_KEY_GENERATED; import static android.app.admin.SecurityLog.TAG_KEY_IMPORT; import static android.app.admin.SecurityLog.TAG_KEY_INTEGRITY_VIOLATION; import static android.app.admin.SecurityLog.TAG_LOGGING_STARTED; import static android.app.admin.SecurityLog.TAG_LOGGING_STOPPED; import static android.app.admin.SecurityLog.TAG_LOG_BUFFER_SIZE_CRITICAL; import static android.app.admin.SecurityLog.TAG_MAX_PASSWORD_ATTEMPTS_SET; import static android.app.admin.SecurityLog.TAG_MAX_SCREEN_LOCK_TIMEOUT_SET; import static android.app.admin.SecurityLog.TAG_MEDIA_MOUNT; import static android.app.admin.SecurityLog.TAG_MEDIA_UNMOUNT; import static android.app.admin.SecurityLog.TAG_OS_SHUTDOWN; import static android.app.admin.SecurityLog.TAG_OS_STARTUP; import static android.app.admin.SecurityLog.TAG_PASSWORD_COMPLEXITY_REQUIRED; import static android.app.admin.SecurityLog.TAG_PASSWORD_COMPLEXITY_SET; import static android.app.admin.SecurityLog.TAG_PASSWORD_EXPIRATION_SET; import static android.app.admin.SecurityLog.TAG_PASSWORD_HISTORY_LENGTH_SET; import static android.app.admin.SecurityLog.TAG_REMOTE_LOCK; import static android.app.admin.SecurityLog.TAG_SYNC_RECV_FILE; import static android.app.admin.SecurityLog.TAG_SYNC_SEND_FILE; import static android.app.admin.SecurityLog.TAG_USER_RESTRICTION_ADDED; import static android.app.admin.SecurityLog.TAG_USER_RESTRICTION_REMOVED; import static android.app.admin.SecurityLog.TAG_WIPE_FAILURE; import static com.android.cts.devicepolicy.TestCertificates.TEST_CA; import static com.android.cts.devicepolicy.TestCertificates.TEST_CA_SUBJECT; import static com.google.common.collect.ImmutableList.of; import static com.google.common.truth.Truth.assertThat; import android.app.admin.DevicePolicyManager; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; import android.content.Context; import android.content.SharedPreferences; import android.os.Parcel; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.support.test.uiautomator.UiDevice; import android.text.TextUtils; import android.util.DebugUtils; import android.util.Log; import androidx.test.InstrumentationRegistry; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import junit.framework.AssertionFailedError; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.crypto.spec.SecretKeySpec; public class SecurityLoggingTest extends BaseDeviceAdminTest { private static final String TAG = "SecurityLoggingTest"; private static final String ARG_BATCH_NUMBER = "batchNumber"; private static final String PREF_KEY_PREFIX = "batch-last-id-"; private static final String PREF_NAME = "batchIds"; // system/core/liblog/event.logtags: 1006 liblog (dropped|1) private static final int TAG_LIBLOG_DROPPED = 1006; private static final String DELEGATE_APP_PKG = "com.android.cts.delegate"; private static final String DELEGATION_SECURITY_LOGGING = "delegation-security-logging"; private static final boolean VERBOSE = false; // For brevity. private static final Class S = String.class; private static final Class L = Long.class; private static final Class I = Integer.class; private static final Map> PAYLOAD_TYPES_MAP = new ImmutableMap.Builder>() .put(TAG_ADB_SHELL_INTERACTIVE, of()) .put(TAG_ADB_SHELL_CMD, of(S)) .put(TAG_SYNC_RECV_FILE, of(S)) .put(TAG_SYNC_SEND_FILE, of(S)) .put(TAG_APP_PROCESS_START, of(S, L, I, I, S, S)) .put(TAG_KEYGUARD_DISMISSED, of()) .put(TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, of(I, I)) .put(TAG_KEYGUARD_SECURED, of()) .put(TAG_OS_STARTUP, of(S, S)) .put(TAG_OS_SHUTDOWN, of()) .put(TAG_LOGGING_STARTED, of()) .put(TAG_LOGGING_STOPPED, of()) .put(TAG_MEDIA_MOUNT, of(S, S)) .put(TAG_MEDIA_UNMOUNT, of(S, S)) .put(TAG_LOG_BUFFER_SIZE_CRITICAL, of()) .put(TAG_PASSWORD_EXPIRATION_SET, of(S, I, I, L)) .put(TAG_PASSWORD_COMPLEXITY_SET, of(S, I, I, I, I, I, I, I, I, I, I)) .put(TAG_PASSWORD_HISTORY_LENGTH_SET, of(S, I, I, I)) .put(TAG_MAX_SCREEN_LOCK_TIMEOUT_SET, of(S, I, I, L)) .put(TAG_MAX_PASSWORD_ATTEMPTS_SET, of(S, I, I, I)) .put(TAG_KEYGUARD_DISABLED_FEATURES_SET, of(S, I, I, I)) .put(TAG_REMOTE_LOCK, of(S, I, I)) .put(TAG_WIPE_FAILURE, of()) .put(TAG_KEY_GENERATED, of(I, S, I)) .put(TAG_KEY_IMPORT, of(I, S, I)) .put(TAG_KEY_DESTRUCTION, of(I, S, I)) .put(TAG_CERT_AUTHORITY_INSTALLED, of(I, S, I)) .put(TAG_CERT_AUTHORITY_REMOVED, of(I, S, I)) .put(TAG_USER_RESTRICTION_ADDED, of(S, I, S)) .put(TAG_USER_RESTRICTION_REMOVED, of(S, I, S)) .put(TAG_CRYPTO_SELF_TEST_COMPLETED, of(I)) .put(TAG_KEY_INTEGRITY_VIOLATION, of(S, I)) .put(TAG_CERT_VALIDATION_FAILURE, of(S)) .put(TAG_CAMERA_POLICY_SET, of(S, I, I, I)) .put(TAG_PASSWORD_COMPLEXITY_REQUIRED, of(S, I, I, I)) .build(); private static final String GENERATED_KEY_ALIAS = "generated_key_alias"; private static final String IMPORTED_KEY_ALIAS = "imported_key_alias"; // Indices of various fields in event payload. private static final int SUCCESS_INDEX = 0; private static final int ALIAS_INDEX = 1; private static final int UID_INDEX = 2; private static final int USERID_INDEX = 2; private static final int SUBJECT_INDEX = 1; private static final int ADMIN_PKG_INDEX = 0; private static final int ADMIN_USER_INDEX = 1; private static final int TARGET_USER_INDEX = 2; private static final int PWD_LEN_INDEX = 3; private static final int PWD_QUALITY_INDEX = 4; private static final int LETTERS_INDEX = 5; private static final int NON_LETTERS_INDEX = 6; private static final int NUMERIC_INDEX = 7; private static final int UPPERCASE_INDEX = 8; private static final int LOWERCASE_INDEX = 9; private static final int SYMBOLS_INDEX = 10; private static final int PWD_EXPIRATION_INDEX = 3; private static final int PWD_HIST_LEN_INDEX = 3; private static final int USER_RESTRICTION_INDEX = 2; private static final int MAX_PWD_ATTEMPTS_INDEX = 3; private static final int KEYGUARD_FEATURES_INDEX = 3; private static final int MAX_SCREEN_TIMEOUT_INDEX = 3; private static final int CAMERA_DISABLED_INDEX = 3; // Value that indicates success in events that have corresponding field in their payload. private static final int SUCCESS_VALUE = 1; private static final int TEST_PWD_LENGTH = 10; // Min number of various character types to use. private static final int TEST_PWD_CHARS = 2; private static final long TEST_PWD_EXPIRATION_TIMEOUT = TimeUnit.DAYS.toMillis(356); private static final int TEST_PWD_HISTORY_LENGTH = 3; private static final int TEST_PWD_MAX_ATTEMPTS = 5; private static final long TEST_MAX_TIME_TO_LOCK = TimeUnit.HOURS.toMillis(1); /** * Test: retrieving security logs can only be done if there's one user on the device or all * secondary users / profiles are affiliated. */ public void testRetrievingSecurityLogsThrowsSecurityException() { try { mDevicePolicyManager.retrieveSecurityLogs(ADMIN_RECEIVER_COMPONENT); fail("did not throw expected SecurityException"); } catch (SecurityException expected) { } } /** * Test: retrieving previous security logs can only be done if there's one user on the device or * all secondary users / profiles are affiliated. */ public void testRetrievingPreviousSecurityLogsThrowsSecurityException() { try { mDevicePolicyManager.retrievePreRebootSecurityLogs(ADMIN_RECEIVER_COMPONENT); fail("did not throw expected SecurityException"); } catch (SecurityException expected) { } } /** * Test: retrieves security logs and verifies that all events generated as a result of host * side actions and by {@link #testGenerateLogs()} are there. */ public void testVerifyGeneratedLogs() throws Exception { forceSecurityLogs(); final List events = getEvents(); verifyAutomaticEventsPresent(events); verifyKeystoreEventsPresent(events); verifyKeyChainEventsPresent(events); verifyAdminEventsPresent(events); verifyAdbShellCommand(events); // Event generated from host side logic if (mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { verifyEventsRedacted(events); } } private void forceSecurityLogs() throws Exception { UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) .executeShellCommand("dpm force-security-logs"); } private void verifyAutomaticEventsPresent(List events) { verifyOsStartupEventPresent(events); verifyLoggingStartedEventPresent(events); verifyCryptoSelfTestEventPresent(events); } private void verifyKeyChainEventsPresent(List events) { verifyCertInstalledEventPresent(events); verifyCertUninstalledEventPresent(events); } private void verifyKeystoreEventsPresent(List events) { verifyKeyGeneratedEventPresent(events, GENERATED_KEY_ALIAS); verifyKeyDeletedEventPresent(events, GENERATED_KEY_ALIAS); verifyKeyImportedEventPresent(events, IMPORTED_KEY_ALIAS); verifyKeyDeletedEventPresent(events, IMPORTED_KEY_ALIAS); } private void verifyAdminEventsPresent(List events) { if (mHasSecureLockScreen) { verifyPasswordComplexityEventsPresent(events); verifyNewStylePasswordComplexityEventPresent(events); } verifyLockingPolicyEventsPresent(events); verifyUserRestrictionEventsPresent(events); verifyCameraPolicyEvents(events); } private void verifyAdbShellCommand(List events) { // Won't be able to locate the command on org-owned devices, as it will be redacted. if (!mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { findEvent("adb command", events, e -> e.getTag() == TAG_ADB_SHELL_CMD && e.getData().equals("whoami")); } } private void verifyEventsRedacted(List events) { final int userId = Process.myUserHandle().getIdentifier(); for (SecurityEvent event : events) { switch (event.getTag()) { case TAG_ADB_SHELL_CMD: assertTrue(TextUtils.isEmpty((String) event.getData())); break; case TAG_APP_PROCESS_START: case TAG_KEY_GENERATED: case TAG_KEY_IMPORT: case TAG_KEY_DESTRUCTION: assertEquals(userId, UserHandle.getUserId(getInt(event, UID_INDEX))); break; case TAG_CERT_AUTHORITY_INSTALLED: case TAG_CERT_AUTHORITY_REMOVED: assertEquals(userId, getInt(event, USERID_INDEX)); break; case TAG_KEY_INTEGRITY_VIOLATION: assertEquals(userId, UserHandle.getUserId(getInt(event, 1))); break; } } } /** * Generates events for positive test cases. */ public void testGenerateLogs() throws Exception { generateKeystoreEvents(); generateKeyChainEvents(); generateAdminEvents(); } private void generateKeyChainEvents() { installCaCert(); uninstallCaCert(); } private void generateKeystoreEvents() throws Exception { generateKey(GENERATED_KEY_ALIAS); deleteKey(GENERATED_KEY_ALIAS); importKey(IMPORTED_KEY_ALIAS); deleteKey(IMPORTED_KEY_ALIAS); } private void generateAdminEvents() { if (mHasSecureLockScreen) { generatePasswordComplexityEvents(); generateNewStylePasswordComplexityEvents(); } generateLockingPolicyEvents(); generateUserRestrictionEvents(); generateCameraPolicyEvents(); } /** * Fetches and checks the events. */ private List getEvents() throws Exception { List events = null; // Retry once after seeping for 1 second, in case "dpm force-security-logs" hasn't taken // effect just yet. for (int i = 0; i < 2 && events == null; i++) { events = mDevicePolicyManager.retrieveSecurityLogs(ADMIN_RECEIVER_COMPONENT); Log.v(TAG, "getEvents(), batch #" + i + ": " + (events == null ? -1 : events.size()) + " events"); if (events == null) sleep(1000); } Log.d(TAG, "getEvents(): received " + events.size() + " events"); if (VERBOSE) dumpSecurityLogs(events); try { verifySecurityLogs(events); } catch (AssertionFailedError e) { dumpSecurityLogs(events); throw e; } return events; } /** * Test: check that there are no gaps between ids in two consecutive batches. Shared preference * is used to store these numbers between test invocations. */ public void testVerifyLogIds() throws Exception { forceSecurityLogs(); final String param = InstrumentationRegistry.getArguments().getString(ARG_BATCH_NUMBER); final int batchId = param == null ? 0 : Integer.parseInt(param); final List events = getEvents(); final SharedPreferences prefs = mContext.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); final long firstId = events.get(0).getId(); if (batchId == 0) { assertEquals("Event id wasn't reset.", 0L, firstId); } else { final String prevBatchLastIdKey = PREF_KEY_PREFIX + (batchId - 1); assertTrue("Last event id from previous batch not found in shared prefs", prefs.contains(prevBatchLastIdKey)); final long prevBatchLastId = prefs.getLong(prevBatchLastIdKey, 0); assertEquals("Event ids aren't consecutive between batches", firstId, prevBatchLastId + 1); } final String currBatchLastIdKey = PREF_KEY_PREFIX + batchId; final long lastId = events.get(events.size() - 1).getId(); prefs.edit().putLong(currBatchLastIdKey, lastId).commit(); } private void verifySecurityLogs(List events) { assertTrue("Unable to get events", events != null && events.size() > 0); // We don't know much about the events, so just call public API methods. for (int i = 0; i < events.size(); i++) { final SecurityEvent event = events.get(i); // Skip liblog dropped event. if (event.getTag() == TAG_LIBLOG_DROPPED) { continue; } verifyPayloadTypes(event); // Test id for monotonically increasing. if (i > 0) { assertEquals("Event IDs are not monotonically increasing within the batch", events.get(i - 1).getId() + 1, event.getId()); } // Test parcelling: flatten to a parcel. Parcel p = Parcel.obtain(); event.writeToParcel(p, 0); p.setDataPosition(0); // Restore from parcel and check contents. final SecurityEvent restored = SecurityEvent.CREATOR.createFromParcel(p); p.recycle(); final int level = event.getLogLevel(); assertTrue(level == LEVEL_INFO || level == LEVEL_WARNING || level == LEVEL_ERROR); // For some events data is encapsulated into Object array. if (event.getData() instanceof Object[]) { assertTrue("Parcelling changed the array returned by getData", Arrays.equals((Object[]) event.getData(), (Object[]) restored.getData())); } else { assertEquals("Parcelling changed the result of getData", event.getData(), restored.getData()); } assertEquals("Parcelling changed the result of getId", event.getId(), restored.getId()); assertEquals("Parcelling changed the result of getTag", event.getTag(), restored.getTag()); assertEquals("Parcelling changed the result of getTimeNanos", event.getTimeNanos(), restored.getTimeNanos()); assertEquals("Parcelling changed the result of describeContents", event.describeContents(), restored.describeContents()); } } private void verifyPayloadTypes(SecurityEvent event) { final List payloadTypes = PAYLOAD_TYPES_MAP.get(event.getTag()); assertNotNull("event type unknown: " + event.getTag(), payloadTypes); if (payloadTypes.size() == 0) { // No payload. assertNull("non-null payload", event.getData()); } else if (payloadTypes.size() == 1) { // Singleton payload. assertTrue(payloadTypes.get(0).isInstance(event.getData())); } else { // Payload is incapsulated into Object[] assertTrue(event.getData() instanceof Object[]); final Object[] dataArray = (Object[]) event.getData(); assertEquals(payloadTypes.size(), dataArray.length); for (int i = 0; i < payloadTypes.size(); i++) { assertTrue(payloadTypes.get(i).isInstance(dataArray[i])); } } } private void verifyOsStartupEventPresent(List events) { final SecurityEvent event = findEvent("os startup", events, TAG_OS_STARTUP); // Verified boot state, empty if running on emulator assertOneOf(ImmutableSet.of("", "green", "yellow", "orange"), getString(event, 0)); // dm-verity mode, empty if it is disabled assertOneOf(ImmutableSet.of("", "enforcing", "eio", "disabled"), getString(event, 1)); } private void assertOneOf(Set allowed, String s) { assertTrue(String.format("\"%s\" is not one of [%s]", s, String.join(", ", allowed)), allowed.contains(s)); } private void verifyCryptoSelfTestEventPresent(List events) { final SecurityEvent event = findEvent("crypto self test complete", events, TAG_CRYPTO_SELF_TEST_COMPLETED); // Success code. assertEquals(1, getInt(event)); } private void verifyLoggingStartedEventPresent(List events) { findEvent("logging started", events, TAG_LOGGING_STARTED); } private SecurityEvent findEvent(String description, List events, int tag) { return findEvent(description, events, e -> e.getTag() == tag); } private SecurityEvent findEvent(String description, List events, Predicate predicate) { final List matches = events.stream().filter(predicate).collect(Collectors.toList()); assertEquals("Invalid number of matching events: " + description, 1, matches.size()); return matches.get(0); } private static Object getDatum(SecurityEvent event, int index) { final Object[] dataArray = (Object[]) event.getData(); return dataArray[index]; } private static String getString(SecurityEvent event, int index) { return (String) getDatum(event, index); } private static int getInt(SecurityEvent event) { return (Integer) event.getData(); } private static int getInt(SecurityEvent event, int index) { return (Integer) getDatum(event, index); } private static long getLong(SecurityEvent event, int index) { return (Long) getDatum(event, index); } /** * Test: Test enabling security logging. This test should be executed after installing a device * owner so that we check that logging is not enabled by default. This test has a side effect: * security logging is enabled after its execution. */ public void testEnablingSecurityLogging() { assertFalse(mDevicePolicyManager.isSecurityLoggingEnabled(ADMIN_RECEIVER_COMPONENT)); mDevicePolicyManager.setSecurityLoggingEnabled(ADMIN_RECEIVER_COMPONENT, true); assertTrue(mDevicePolicyManager.isSecurityLoggingEnabled(ADMIN_RECEIVER_COMPONENT)); } /** * Test: Test disabling security logging. This test has a side effect: security logging is * disabled after its execution. */ public void testDisablingSecurityLogging() { mDevicePolicyManager.setSecurityLoggingEnabled(ADMIN_RECEIVER_COMPONENT, false); assertFalse(mDevicePolicyManager.isSecurityLoggingEnabled(ADMIN_RECEIVER_COMPONENT)); // Verify that logs are actually not available. assertNull(mDevicePolicyManager.retrieveSecurityLogs(ADMIN_RECEIVER_COMPONENT)); } /** * Test: retrieving security logs should be rate limited - subsequent attempts should return * null. */ public void testSecurityLoggingRetrievalRateLimited() { final List logs = mDevicePolicyManager.retrieveSecurityLogs( ADMIN_RECEIVER_COMPONENT); // if logs is null it means that that attempt was rate limited => test PASS if (logs != null) { assertNull(mDevicePolicyManager.retrieveSecurityLogs(ADMIN_RECEIVER_COMPONENT)); assertNull(mDevicePolicyManager.retrieveSecurityLogs(ADMIN_RECEIVER_COMPONENT)); } } public void testSetDelegateScope_delegationSecurityLogging() { setDelegatedScopes(DELEGATE_APP_PKG, Arrays.asList(DELEGATION_SECURITY_LOGGING)); assertThat(mDevicePolicyManager.getDelegatedScopes( ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG)).contains(DELEGATION_SECURITY_LOGGING); } public void testSetDelegateScope_noDelegation() { setDelegatedScopes(DELEGATE_APP_PKG, Arrays.asList()); assertThat(mDevicePolicyManager.getDelegatedScopes( ADMIN_RECEIVER_COMPONENT, DELEGATE_APP_PKG)) .doesNotContain(DELEGATION_SECURITY_LOGGING); } private void generateKey(String keyAlias) throws Exception { final KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore"); generator.initialize( new KeyGenParameterSpec.Builder(keyAlias, KeyProperties.PURPOSE_SIGN).build()); final KeyPair keyPair = generator.generateKeyPair(); assertNotNull(keyPair); } private void verifyKeyGeneratedEventPresent(List events, String alias) { findEvent("key generated", events, e -> e.getTag() == TAG_KEY_GENERATED && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE && getString(e, ALIAS_INDEX).contains(alias) && getInt(e, UID_INDEX) == Process.myUid()); } private void importKey(String alias) throws Exception{ final KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); ks.setEntry(alias, new KeyStore.SecretKeyEntry(new SecretKeySpec(new byte[32], "AES")), new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT).build()); } private void verifyKeyImportedEventPresent(List events, String alias) { findEvent("key imported", events, e -> e.getTag() == TAG_KEY_IMPORT && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE && getString(e, ALIAS_INDEX).contains(alias) && getInt(e, UID_INDEX) == Process.myUid()); } private void deleteKey(String keyAlias) throws Exception { final KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); ks.deleteEntry(keyAlias); } private void verifyKeyDeletedEventPresent(List events, String alias) { findEvent("key deleted", events, e -> e.getTag() == TAG_KEY_DESTRUCTION && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE && getString(e, ALIAS_INDEX).contains(alias) && getInt(e, UID_INDEX) == Process.myUid()); } private void installCaCert() { assertTrue( mDevicePolicyManager.installCaCert(ADMIN_RECEIVER_COMPONENT, TEST_CA.getBytes())); } private void verifyCertInstalledEventPresent(List events) { findEvent("cert authority installed", events, e -> e.getTag() == TAG_CERT_AUTHORITY_INSTALLED && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE && getString(e, SUBJECT_INDEX).equals(TEST_CA_SUBJECT)); } private void uninstallCaCert() { mDevicePolicyManager.uninstallCaCert(ADMIN_RECEIVER_COMPONENT, TEST_CA.getBytes()); } private void verifyCertUninstalledEventPresent(List events) { findEvent("cert authority removed", events, e -> e.getTag() == TAG_CERT_AUTHORITY_REMOVED && getInt(e, SUCCESS_INDEX) == SUCCESS_VALUE && getString(e, SUBJECT_INDEX).equals(TEST_CA_SUBJECT)); } private void generatePasswordComplexityEvents() { DevicePolicyManager dpm = getDpmToGenerateEvents(); dpm.setPasswordQuality(ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX); dpm.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, TEST_PWD_LENGTH); dpm.setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, TEST_PWD_CHARS); dpm.setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, TEST_PWD_CHARS); dpm.setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, TEST_PWD_CHARS); dpm.setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, TEST_PWD_CHARS); dpm.setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, TEST_PWD_CHARS); dpm.setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_PWD_CHARS); } private void generateNewStylePasswordComplexityEvents() { DevicePolicyManager dpm = getDpmToGenerateEvents(); dpm.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH); } private void verifyPasswordComplexityEventsPresent(List events) { final int userId = Process.myUserHandle().getIdentifier(); // This reflects default values for password complexity event payload fields. final Object[] expectedPayload = new Object[] { ADMIN_RECEIVER_COMPONENT.getPackageName(), // admin package userId, // admin user userId, // target user 0, // default password length 0, // default password quality 1, // default min letters 0, // default min non-letters 1, // default min numeric 0, // default min uppercase 0, // default min lowercase 1, // default min symbols }; // The order should be consistent with the order in generatePasswordComplexityEvents(), so // that the expected values change in the same sequence as when setting password policies. expectedPayload[PWD_QUALITY_INDEX] = PASSWORD_QUALITY_COMPLEX; findPasswordComplexityEvent("set pwd quality", events, expectedPayload); expectedPayload[PWD_LEN_INDEX] = TEST_PWD_LENGTH; findPasswordComplexityEvent("set pwd length", events, expectedPayload); expectedPayload[LETTERS_INDEX] = TEST_PWD_CHARS; findPasswordComplexityEvent("set pwd min letters", events, expectedPayload); expectedPayload[NON_LETTERS_INDEX] = TEST_PWD_CHARS; findPasswordComplexityEvent("set pwd min non-letters", events, expectedPayload); expectedPayload[UPPERCASE_INDEX] = TEST_PWD_CHARS; findPasswordComplexityEvent("set pwd min uppercase", events, expectedPayload); expectedPayload[LOWERCASE_INDEX] = TEST_PWD_CHARS; findPasswordComplexityEvent("set pwd min lowercase", events, expectedPayload); expectedPayload[NUMERIC_INDEX] = TEST_PWD_CHARS; findPasswordComplexityEvent("set pwd min numeric", events, expectedPayload); expectedPayload[SYMBOLS_INDEX] = TEST_PWD_CHARS; findPasswordComplexityEvent("set pwd min symbols", events, expectedPayload); } private void verifyNewStylePasswordComplexityEventPresent(List events) { final int userId = Process.myUserHandle().getIdentifier(); // This reflects default values for password complexity event payload fields. final Object[] expectedPayload = new Object[] { ADMIN_RECEIVER_COMPONENT.getPackageName(), // admin package userId, // admin user userId, // target user PASSWORD_COMPLEXITY_HIGH // password complexity }; findNewStylePasswordComplexityEvent("require password complexity", events, expectedPayload); } private void generateLockingPolicyEvents() { DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); if (mHasSecureLockScreen) { dpm.setPasswordExpirationTimeout(ADMIN_RECEIVER_COMPONENT, TEST_PWD_EXPIRATION_TIMEOUT); dpm.setPasswordHistoryLength(ADMIN_RECEIVER_COMPONENT, TEST_PWD_HISTORY_LENGTH); dpm.setMaximumFailedPasswordsForWipe(ADMIN_RECEIVER_COMPONENT, TEST_PWD_MAX_ATTEMPTS); } dpm.setKeyguardDisabledFeatures(ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_FINGERPRINT); dpm.setMaximumTimeToLock(ADMIN_RECEIVER_COMPONENT, TEST_MAX_TIME_TO_LOCK); dpm.lockNow(); } private void verifyLockingPolicyEventsPresent(List events) { final int userId = Process.myUserHandle().getIdentifier(); final String packageName = ADMIN_RECEIVER_COMPONENT.getPackageName(); if (mHasSecureLockScreen) { findEvent("set password expiration", events, e -> e.getTag() == TAG_PASSWORD_EXPIRATION_SET && getString(e, ADMIN_PKG_INDEX).equals(packageName) && getInt(e, ADMIN_USER_INDEX) == userId && getInt(e, TARGET_USER_INDEX) == userId && getLong(e, PWD_EXPIRATION_INDEX) == TEST_PWD_EXPIRATION_TIMEOUT); findEvent("set password history length", events, e -> e.getTag() == TAG_PASSWORD_HISTORY_LENGTH_SET && getString(e, ADMIN_PKG_INDEX).equals(packageName) && getInt(e, ADMIN_USER_INDEX) == userId && getInt(e, TARGET_USER_INDEX) == userId && getInt(e, PWD_HIST_LEN_INDEX) == TEST_PWD_HISTORY_LENGTH); findEvent("set password attempts", events, e -> e.getTag() == TAG_MAX_PASSWORD_ATTEMPTS_SET && getString(e, ADMIN_PKG_INDEX).equals(packageName) && getInt(e, ADMIN_USER_INDEX) == userId && getInt(e, TARGET_USER_INDEX) == userId && getInt(e, MAX_PWD_ATTEMPTS_INDEX) == TEST_PWD_MAX_ATTEMPTS); } findEvent("set keyguard disabled features", events, e -> e.getTag() == TAG_KEYGUARD_DISABLED_FEATURES_SET && getString(e, ADMIN_PKG_INDEX).equals(packageName) && getInt(e, ADMIN_USER_INDEX) == userId && getInt(e, TARGET_USER_INDEX) == userId && getInt(e, KEYGUARD_FEATURES_INDEX) == KEYGUARD_DISABLE_FINGERPRINT); findEvent("set screen lock timeout", events, e -> e.getTag() == TAG_MAX_SCREEN_LOCK_TIMEOUT_SET && getString(e, ADMIN_PKG_INDEX).equals(packageName) && getInt(e, ADMIN_USER_INDEX) == userId && getInt(e, TARGET_USER_INDEX) == userId && getLong(e, MAX_SCREEN_TIMEOUT_INDEX) == TEST_MAX_TIME_TO_LOCK); findEvent("set screen lock timeout", events, e -> e.getTag() == TAG_REMOTE_LOCK && getString(e, ADMIN_PKG_INDEX).equals(packageName) && getInt(e, ADMIN_USER_INDEX) == userId); } private void findPasswordComplexityEvent( String description, List events, Object[] expectedPayload) { findEvent(description, events, byTagAndPayload(TAG_PASSWORD_COMPLEXITY_SET, expectedPayload)); } private void findNewStylePasswordComplexityEvent( String description, List events, Object[] expectedPayload) { findEvent(description, events, byTagAndPayload(TAG_PASSWORD_COMPLEXITY_REQUIRED, expectedPayload)); } private Predicate byTagAndPayload(int expectedTag, Object[] expectedPayload) { return (event) -> { boolean tagMatch = event.getTag() == expectedTag; if (!tagMatch) return false; Object[] payload = (Object[]) event.getData(); boolean payloadMatch = Arrays.equals(payload, expectedPayload); if (!payloadMatch) { Log.w(TAG, "Found event (id=" + event.getId() + ") with tag " + eventLogtoString(event.getTag()) + ", but invalid payload: " + "expected=" + Arrays.toString(expectedPayload) + ", actual=" + Arrays.toString(payload)); } else if (VERBOSE) { Log.v(TAG, "Found event (id=" + event.getId() + ") with tag " + eventLogtoString(event.getTag()) + ", and expected payload (" + Arrays.toString(payload) + ")"); } return payloadMatch; }; } private void generateUserRestrictionEvents() { DevicePolicyManager dpm = getDpmToGenerateEvents(); dpm.addUserRestriction(ADMIN_RECEIVER_COMPONENT, UserManager.DISALLOW_PRINTING); dpm.clearUserRestriction(ADMIN_RECEIVER_COMPONENT, UserManager.DISALLOW_PRINTING); } private void verifyUserRestrictionEventsPresent(List events) { findUserRestrictionEvent("set user restriction", events, TAG_USER_RESTRICTION_ADDED); findUserRestrictionEvent("clear user restriction", events, TAG_USER_RESTRICTION_REMOVED); } private void findUserRestrictionEvent(String description, List events, int tag) { final int userId = Process.myUserHandle().getIdentifier(); findEvent(description, events, e -> e.getTag() == tag && getString(e, ADMIN_PKG_INDEX).equals( ADMIN_RECEIVER_COMPONENT.getPackageName()) && getInt(e, ADMIN_USER_INDEX) == userId && UserManager.DISALLOW_PRINTING.equals(getString(e, USER_RESTRICTION_INDEX))); } private void generateCameraPolicyEvents() { DevicePolicyManager dpm = getDpmToGenerateEvents(); dpm.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, true); dpm.setCameraDisabled(ADMIN_RECEIVER_COMPONENT, false); } private void verifyCameraPolicyEvents(List events) { final int userId = Process.myUserHandle().getIdentifier(); findEvent("set camera disabled", events, e -> e.getTag() == TAG_CAMERA_POLICY_SET && getString(e, ADMIN_PKG_INDEX).equals( ADMIN_RECEIVER_COMPONENT.getPackageName()) && getInt(e, ADMIN_USER_INDEX) == userId && getInt(e, TARGET_USER_INDEX) == userId && getInt(e, CAMERA_DISABLED_INDEX) == 1); findEvent("set camera enabled", events, e -> e.getTag() == TAG_CAMERA_POLICY_SET && getString(e, ADMIN_PKG_INDEX).equals( ADMIN_RECEIVER_COMPONENT.getPackageName()) && getInt(e, ADMIN_USER_INDEX) == userId && getInt(e, TARGET_USER_INDEX) == userId && getInt(e, CAMERA_DISABLED_INDEX) == 0); } private DevicePolicyManager getDpmToGenerateEvents() { // It must use the dpm for the current user, as mDevicePolicyManager tunnels the calls to // the device owner user on headless system user, which would cause a mismatch in the events return mContext.getSystemService(DevicePolicyManager.class); } private static String eventLogtoString(int log) { return DebugUtils.constantToString(SecurityLog.class, "TAG_", log); } private static String toString(SecurityEvent event) { return "Event[id=" + event.getId() + ",tag=" + eventLogtoString(event.getTag()) + "]"; } private void dumpSecurityLogs(List events) { Log.d(TAG, "Security events dump (" + events.size() + " events):"); events.forEach((event) -> Log.d(TAG, toString(event))); } }