blob: d8fd8df77a41d9c78011fbdedcf58ea613af3192 [file] [log] [blame]
/*
* 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<String> S = String.class;
private static final Class<Long> L = Long.class;
private static final Class<Integer> I = Integer.class;
private static final Map<Integer, List<Class>> PAYLOAD_TYPES_MAP =
new ImmutableMap.Builder<Integer, List<Class>>()
.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<SecurityEvent> 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<SecurityEvent> events) {
verifyOsStartupEventPresent(events);
verifyLoggingStartedEventPresent(events);
verifyCryptoSelfTestEventPresent(events);
}
private void verifyKeyChainEventsPresent(List<SecurityEvent> events) {
verifyCertInstalledEventPresent(events);
verifyCertUninstalledEventPresent(events);
}
private void verifyKeystoreEventsPresent(List<SecurityEvent> 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<SecurityEvent> events) {
if (mHasSecureLockScreen) {
verifyPasswordComplexityEventsPresent(events);
verifyNewStylePasswordComplexityEventPresent(events);
}
verifyLockingPolicyEventsPresent(events);
verifyUserRestrictionEventsPresent(events);
verifyCameraPolicyEvents(events);
}
private void verifyAdbShellCommand(List<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> getEvents() throws Exception {
List<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> 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<Class> 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<SecurityEvent> 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<String> allowed, String s) {
assertTrue(String.format("\"%s\" is not one of [%s]", s, String.join(", ", allowed)),
allowed.contains(s));
}
private void verifyCryptoSelfTestEventPresent(List<SecurityEvent> 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<SecurityEvent> events) {
findEvent("logging started", events, TAG_LOGGING_STARTED);
}
private SecurityEvent findEvent(String description, List<SecurityEvent> events, int tag) {
return findEvent(description, events, e -> e.getTag() == tag);
}
private List<SecurityEvent> findEvents(List<SecurityEvent> events,
Predicate<SecurityEvent> predicate) {
return events.stream().filter(predicate).collect(Collectors.toList());
}
private SecurityEvent findEvent(String description, List<SecurityEvent> events,
Predicate<SecurityEvent> predicate) {
final List<SecurityEvent> matches = findEvents(events, predicate);
assertEquals("Invalid number of matching events: " + description, 1, matches.size());
return matches.get(0);
}
private void assertNumberEvents(String description, List<SecurityEvent> events,
Predicate<SecurityEvent> predicate, int expectedSize) {
assertEquals("Invalid number of matching events: " + description, expectedSize,
findEvents(events, predicate).size());
}
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<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> 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;
assertPasswordComplexityEvent("set pwd quality", events, expectedPayload);
expectedPayload[PWD_LEN_INDEX] = TEST_PWD_LENGTH;
assertPasswordComplexityEvent("set pwd length", events, expectedPayload);
expectedPayload[LETTERS_INDEX] = TEST_PWD_CHARS;
assertPasswordComplexityEvent("set pwd min letters", events, expectedPayload);
expectedPayload[NON_LETTERS_INDEX] = TEST_PWD_CHARS;
assertPasswordComplexityEvent("set pwd min non-letters", events, expectedPayload);
expectedPayload[UPPERCASE_INDEX] = TEST_PWD_CHARS;
assertPasswordComplexityEvent("set pwd min uppercase", events, expectedPayload);
expectedPayload[LOWERCASE_INDEX] = TEST_PWD_CHARS;
assertPasswordComplexityEvent("set pwd min lowercase", events, expectedPayload);
expectedPayload[NUMERIC_INDEX] = TEST_PWD_CHARS;
assertPasswordComplexityEvent("set pwd min numeric", events, expectedPayload);
expectedPayload[SYMBOLS_INDEX] = TEST_PWD_CHARS;
assertPasswordComplexityEvent("set pwd min symbols", events, expectedPayload);
}
private void verifyNewStylePasswordComplexityEventPresent(List<SecurityEvent> 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<SecurityEvent> 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 assertPasswordComplexityEvent(
String description, List<SecurityEvent> events, Object[] expectedPayload) {
int expectedSize = mIsAutomotive ? 0 : 1;
assertNumberEvents(description, events,
byTagAndPayload(TAG_PASSWORD_COMPLEXITY_SET, expectedPayload), expectedSize);
}
private void findNewStylePasswordComplexityEvent(
String description, List<SecurityEvent> events, Object[] expectedPayload) {
findEvent(description, events,
byTagAndPayload(TAG_PASSWORD_COMPLEXITY_REQUIRED, expectedPayload));
}
private Predicate<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> 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<SecurityEvent> events) {
Log.d(TAG, "Security events dump (" + events.size() + " events):");
events.forEach((event) -> Log.d(TAG, toString(event)));
}
}