diff options
9 files changed, 161 insertions, 3 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f93eab5b38ca..20a621ba8bf2 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3683,6 +3683,7 @@ package android.content { 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 e8031a374310..0bcbb8e1868c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -19,6 +19,7 @@ package android.content; 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 @@ public class Intent implements Parcelable, Cloneable { "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 fb82edc9166a..e7df19ce55ca 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 7fb3e001c4c3..9a76ebd148aa 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 @@ package com.android.server.locksettings; 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_ENCRY 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { 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 @@ public class LockSettingsService extends ILockSettings.Stub { setCurrentLskfBasedProtectorId(newProtectorId, userId); LockPatternUtils.invalidateCredentialTypeCache(); synchronizeUnifiedChallengeForProfiles(userId, profilePasswords); + sendMainUserCredentialChangedNotificationIfNeeded(userId); setUserPasswordMetrics(credential, userId); mUnifiedProfilePasswordCache.removePassword(userId); @@ -3071,6 +3080,24 @@ public class LockSettingsService extends ILockSettings.Stub { 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 6d123ccebc7c..158d444bcff2 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.Environment; 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 @@ class LockSettingsStorage { 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 @@ class LockSettingsStorage { } } + 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 6986cab72f56..e59b5ea027ed 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 @@ public abstract class BaseLockSettingsServiceTests { } 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 ee076c6bcf4b..296d2cba83dd 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 static org.mockito.Mockito.mock; 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 com.android.server.pm.UserManagerInternal; 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 @@ public class LockSettingsServiceTestable extends LockSettingsService { 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 705359708bc7..4b22652a3f21 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.verify; 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 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @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 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { } @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 @@ public class LockSettingsServiceTests extends BaseLockSettingsServiceTests { 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 fa3c7a4c4769..c01d0f644983 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 class LockSettingsStorageTestable extends LockSettingsStorage { 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 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { }).when(mPersistentDataBlockManager).getFrpCredentialHandle(); } + void setTestFactoryResetProtectionState(boolean active) { + mIsFactoryResetProtectionActive = active; + } + @Override File getChildProfileLockFile(int userId) { return remapToStorageDir(super.getChildProfileLockFile(userId)); @@ -101,4 +106,9 @@ public class LockSettingsStorageTestable extends LockSettingsStorage { mappedPath.getParentFile().mkdirs(); return mappedPath; } + + @Override + public boolean isFactoryResetProtectionActive() { + return mIsFactoryResetProtectionActive; + } } |