Merge "Update LockSettingsService to support hardened FRP" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f93eab5..20a621ba 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3683,6 +3683,7 @@
field public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS";
field @Deprecated public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
field public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA";
+ field @FlaggedApi("android.security.frp_enforcement") public static final String ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED = "android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_MANAGE_APP_PERMISSION = "android.intent.action.MANAGE_APP_PERMISSION";
field @Deprecated public static final String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS";
field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e8031a3..0bcbb8e 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -19,6 +19,7 @@
import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
import static android.content.ContentProvider.maybeAddUserId;
import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
+import static android.security.Flags.FLAG_FRP_ENFORCEMENT;
import static android.service.chooser.Flags.FLAG_ENABLE_SHARESHEET_METADATA_EXTRA;
import android.Manifest;
@@ -3902,6 +3903,26 @@
"android.intent.action.ACTION_IDLE_MAINTENANCE_END";
/**
+ * Broadcast Action: A broadcast sent to the main user when the main user changes their
+ * Lock Screen Knowledge Factor, either because they changed the current value, or because
+ * they added or removed it.
+ *
+ * <p class="note">At present, this intent is only broadcast to listeners with the
+ * CONFIGURE_FACTORY_RESET_PROTECTION signature|privileged permiession.</p>
+ *
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ *
+ * @hide
+ */
+ @FlaggedApi(FLAG_FRP_ENFORCEMENT)
+ @SystemApi
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ @BroadcastBehavior(protectedBroadcast = true)
+ public static final String
+ ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED =
+ "android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED";
+
+ /**
* Broadcast Action: a remote intent is to be broadcasted.
*
* A remote intent is used for remote RPC between devices. The remote intent
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index fb82edc..e7df19c 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -836,6 +836,7 @@
<protected-broadcast android:name="android.intent.action.PROFILE_AVAILABLE" />
<protected-broadcast android:name="android.intent.action.PROFILE_UNAVAILABLE" />
<protected-broadcast android:name="android.app.action.CONSOLIDATED_NOTIFICATION_POLICY_CHANGED" />
+ <protected-broadcast android:name="android.intent.action.MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 7fb3e00..9a76ebd 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -18,6 +18,7 @@
import static android.security.Flags.reportPrimaryAuthAttempts;
import static android.Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE;
+import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION;
import static android.Manifest.permission.MANAGE_BIOMETRIC;
import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
import static android.Manifest.permission.SET_INITIAL_LOCK;
@@ -27,6 +28,7 @@
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
import static android.content.Context.KEYGUARD_SERVICE;
+import static android.content.Intent.ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
@@ -1201,8 +1203,9 @@
final boolean inSetupWizard = Settings.Secure.getIntForUser(cr,
Settings.Secure.USER_SETUP_COMPLETE, 0, mainUserId) == 0;
- final boolean secureFrp = Settings.Global.getInt(cr,
- Settings.Global.SECURE_FRP_MODE, 0) == 1;
+ final boolean secureFrp = android.security.Flags.frpEnforcement()
+ ? mStorage.isFactoryResetProtectionActive()
+ : (Settings.Global.getInt(cr, Settings.Global.SECURE_FRP_MODE, 0) == 1);
if (inSetupWizard && secureFrp) {
throw new SecurityException("Cannot change credential in SUW while factory reset"
@@ -2332,8 +2335,13 @@
synchronized (mSpManager) {
if (isSpecialUserId(userId)) {
- return mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(),
+ response = mSpManager.verifySpecialUserCredential(userId, getGateKeeperService(),
credential, progressCallback);
+ if (android.security.Flags.frpEnforcement() && response.isMatched()
+ && userId == USER_FRP) {
+ mStorage.deactivateFactoryResetProtectionWithoutSecret();
+ }
+ return response;
}
long protectorId = getCurrentLskfBasedProtectorId(userId);
@@ -3054,6 +3062,7 @@
setCurrentLskfBasedProtectorId(newProtectorId, userId);
LockPatternUtils.invalidateCredentialTypeCache();
synchronizeUnifiedChallengeForProfiles(userId, profilePasswords);
+ sendMainUserCredentialChangedNotificationIfNeeded(userId);
setUserPasswordMetrics(credential, userId);
mUnifiedProfilePasswordCache.removePassword(userId);
@@ -3071,6 +3080,24 @@
return newProtectorId;
}
+ private void sendMainUserCredentialChangedNotificationIfNeeded(int userId) {
+ if (!android.security.Flags.frpEnforcement()) {
+ return;
+ }
+
+ if (userId != mInjector.getUserManagerInternal().getMainUserId()) {
+ return;
+ }
+
+ sendBroadcast(new Intent(ACTION_MAIN_USER_LOCKSCREEN_KNOWLEDGE_FACTOR_CHANGED),
+ UserHandle.of(userId), CONFIGURE_FACTORY_RESET_PROTECTION);
+ }
+
+ @VisibleForTesting
+ void sendBroadcast(Intent intent, UserHandle userHandle, String permission) {
+ mContext.sendBroadcastAsUser(intent, userHandle, permission, /* options */ null);
+ }
+
private void removeBiometricsForUser(int userId) {
removeAllFingerprintForUser(userId);
removeAllFaceForUser(userId);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 6d123cc..158d444 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -34,6 +34,7 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import android.service.persistentdata.PersistentDataBlockManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
@@ -587,6 +588,10 @@
return mPersistentDataBlockManagerInternal;
}
+ /**
+ * Writes main user credential handle to the persistent data block, to enable factory reset
+ * protection to be deactivated with the credential.
+ */
public void writePersistentDataBlock(int persistentType, int userId, int qualityForUi,
byte[] payload) {
PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager();
@@ -610,6 +615,31 @@
}
}
+ public void deactivateFactoryResetProtectionWithoutSecret() {
+ PersistentDataBlockManagerInternal persistentDataBlock = getPersistentDataBlockManager();
+ if (persistentDataBlock != null) {
+ persistentDataBlock.deactivateFactoryResetProtectionWithoutSecret();
+ } else {
+ Slog.wtf(TAG, "Failed to get PersistentDataBlockManagerInternal");
+ }
+ }
+
+ public boolean isFactoryResetProtectionActive() {
+ PersistentDataBlockManager persistentDataBlockManager =
+ mContext.getSystemService(PersistentDataBlockManager.class);
+ if (persistentDataBlockManager != null) {
+ return persistentDataBlockManager.isFactoryResetProtectionActive();
+ } else {
+ Slog.wtf(TAG, "Failed to get PersistentDataBlockManager");
+ // This should never happen, but in the event it does, let's not block the user. This
+ // may be the wrong call, since if an attacker can find a way to prevent us from
+ // getting the PersistentDataBlockManager they can defeat FRP, but if they can block
+ // access to PersistentDataBlockManager they must have compromised the system and we've
+ // probably already lost this battle.
+ return false;
+ }
+ }
+
/**
* Provides a concrete data structure to represent the minimal information from
* a user's LSKF-based SP protector that is needed to verify the user's LSKF,
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 6986cab..e59b5ea 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -270,6 +270,9 @@
}
protected void setSecureFrpMode(boolean secure) {
+ if (android.security.Flags.frpEnforcement()) {
+ mStorage.setTestFactoryResetProtectionState(secure);
+ }
Settings.Secure.putIntForUser(mContext.getContentResolver(),
Settings.Secure.SECURE_FRP_MODE, secure ? 1 : 0, UserHandle.USER_SYSTEM);
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index ee076c6..296d2cb 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -21,12 +21,14 @@
import android.app.IActivityManager;
import android.app.admin.DeviceStateCache;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.UserInfo;
import android.hardware.authsecret.IAuthSecret;
import android.os.Handler;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.os.storage.IStorageManager;
import android.security.KeyStore;
import android.security.keystore.KeyPermanentlyInvalidatedException;
@@ -41,6 +43,9 @@
import java.io.FileNotFoundException;
public class LockSettingsServiceTestable extends LockSettingsService {
+ private Intent mSavedFrpNotificationIntent = null;
+ private UserHandle mSavedFrpNotificationUserHandle = null;
+ private String mSavedFrpNotificationPermission = null;
public static class MockInjector extends LockSettingsService.Injector {
@@ -218,4 +223,29 @@
mAuthSecret = null;
}
}
+
+ @Override
+ void sendBroadcast(Intent intent, UserHandle userHandle, String permission) {
+ mSavedFrpNotificationIntent = intent;
+ mSavedFrpNotificationUserHandle = userHandle;
+ mSavedFrpNotificationPermission = permission;
+ }
+
+ String getSavedFrpNotificationPermission() {
+ return mSavedFrpNotificationPermission;
+ }
+
+ UserHandle getSavedFrpNotificationUserHandle() {
+ return mSavedFrpNotificationUserHandle;
+ }
+
+ Intent getSavedFrpNotificationIntent() {
+ return mSavedFrpNotificationIntent;
+ }
+
+ void clearRecordedFrpNotificationData() {
+ mSavedFrpNotificationIntent = null;
+ mSavedFrpNotificationPermission = null;
+ mSavedFrpNotificationUserHandle = null;
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index 7053597..4b22652 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -16,6 +16,7 @@
package com.android.server.locksettings;
+import static android.Manifest.permission.CONFIGURE_FACTORY_RESET_PROTECTION;
import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
@@ -39,7 +40,9 @@
import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
+import android.content.Intent;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.gatekeeper.GateKeeperResponse;
@@ -239,6 +242,12 @@
}
@Test
+ public void testSetLockCredential_forPrimaryUser_sendsFrpNotification() throws Exception {
+ setCredential(PRIMARY_USER_ID, newPassword("password"));
+ checkRecordedFrpNotificationIntent();
+ }
+
+ @Test
public void testSetLockCredential_forPrimaryUser_sendsCredentials() throws Exception {
setCredential(PRIMARY_USER_ID, newPassword("password"));
verify(mRecoverableKeyStoreManager)
@@ -323,6 +332,15 @@
}
@Test
+ public void testClearLockCredential_sendsFrpNotification() throws Exception {
+ setCredential(PRIMARY_USER_ID, newPassword("password"));
+ checkRecordedFrpNotificationIntent();
+ mService.clearRecordedFrpNotificationData();
+ clearCredential(PRIMARY_USER_ID, newPassword("password"));
+ checkRecordedFrpNotificationIntent();
+ }
+
+ @Test
public void testSetLockCredential_forUnifiedToSeparateChallengeProfile_sendsNewCredentials()
throws Exception {
final LockscreenCredential parentPassword = newPassword("parentPassword");
@@ -519,6 +537,23 @@
mService.setString(null, "value", 0);
}
+ private void checkRecordedFrpNotificationIntent() {
+ if (android.security.Flags.frpEnforcement()) {
+ Intent savedNotificationIntent = mService.getSavedFrpNotificationIntent();
+ assertNotNull(savedNotificationIntent);
+ UserHandle userHandle = mService.getSavedFrpNotificationUserHandle();
+ assertEquals(userHandle,
+ UserHandle.of(mInjector.getUserManagerInternal().getMainUserId()));
+
+ String permission = mService.getSavedFrpNotificationPermission();
+ assertEquals(CONFIGURE_FACTORY_RESET_PROTECTION, permission);
+ } else {
+ assertNull(mService.getSavedFrpNotificationIntent());
+ assertNull(mService.getSavedFrpNotificationUserHandle());
+ assertNull(mService.getSavedFrpNotificationPermission());
+ }
+ }
+
private void checkPasswordHistoryLength(int userId, int expectedLen) {
String history = mService.getString(LockPatternUtils.PASSWORD_HISTORY_KEY, "", userId);
String[] hashes = TextUtils.split(history, LockPatternUtils.PASSWORD_HISTORY_DELIMITER);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
index fa3c7a4c..c01d0f6 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTestable.java
@@ -35,6 +35,7 @@
public final File mStorageDir;
public PersistentDataBlockManagerInternal mPersistentDataBlockManager;
private byte[] mPersistentData;
+ private boolean mIsFactoryResetProtectionActive = false;
public LockSettingsStorageTestable(Context context, File storageDir) {
super(context);
@@ -63,6 +64,10 @@
}).when(mPersistentDataBlockManager).getFrpCredentialHandle();
}
+ void setTestFactoryResetProtectionState(boolean active) {
+ mIsFactoryResetProtectionActive = active;
+ }
+
@Override
File getChildProfileLockFile(int userId) {
return remapToStorageDir(super.getChildProfileLockFile(userId));
@@ -101,4 +106,9 @@
mappedPath.getParentFile().mkdirs();
return mappedPath;
}
+
+ @Override
+ public boolean isFactoryResetProtectionActive() {
+ return mIsFactoryResetProtectionActive;
+ }
}