Loading core/java/com/android/internal/widget/ILockSettings.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -45,4 +45,10 @@ interface ILockSettings { void systemReady(); void userPresent(int userId); int getStrongAuthForUser(int userId); long addEscrowToken(in byte[] token, int userId); boolean removeEscrowToken(long handle, int userId); boolean isEscrowTokenActive(long handle, int userId); boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, in byte[] token, int userId); void unlockUserWithToken(long tokenHandle, in byte[] token, int userId); } core/java/com/android/internal/widget/LockPatternUtils.java +104 −2 Original line number Diff line number Diff line Loading @@ -773,7 +773,7 @@ public class LockPatternUtils { getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword, userHandle); addEncryptionPassword(password, computedQuality, userHandle); updateEncryptionPasswordIfNeeded(password, computedQuality, userHandle); updatePasswordHistory(password, userHandle); } catch (RemoteException re) { // Cant do much Loading @@ -781,7 +781,11 @@ public class LockPatternUtils { } } private void addEncryptionPassword(String password, int quality, int userHandle) { /** * Update device encryption password if calling user is USER_SYSTEM and device supports * encryption. */ private void updateEncryptionPasswordIfNeeded(String password, int quality, int userHandle) { // Update the device encryption password. if (userHandle == UserHandle.USER_SYSTEM && LockPatternUtils.isDeviceEncryptionEnabled()) { Loading Loading @@ -1401,6 +1405,104 @@ public class LockPatternUtils { } } /** * Create an escrow token for the current user, which can later be used to unlock FBE * or change user password. * * After adding, if the user currently has lockscreen password, he will need to perform a * confirm credential operation in order to activate the token for future use. If the user * has no secure lockscreen, then the token is activated immediately. * * @return a unique 64-bit token handle which is needed to refer to this token later. */ public long addEscrowToken(byte[] token, int userId) { try { return getLockSettings().addEscrowToken(token, userId); } catch (RemoteException re) { return 0L; } } /** * Remove an escrow token. * @return true if the given handle refers to a valid token previously returned from * {@link #addEscrowToken}, whether it's active or not. return false otherwise. */ public boolean removeEscrowToken(long handle, int userId) { try { return getLockSettings().removeEscrowToken(handle, userId); } catch (RemoteException re) { return false; } } /** * Check if the given escrow token is active or not. Only active token can be used to call * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken} */ public boolean isEscrowTokenActive(long handle, int userId) { try { return getLockSettings().isEscrowTokenActive(handle, userId); } catch (RemoteException re) { return false; } } public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, byte[] token, int userId) { try { if (type != CREDENTIAL_TYPE_NONE) { if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) { throw new IllegalArgumentException("password must not be null and at least " + "of length " + MIN_LOCK_PASSWORD_SIZE); } final int computedQuality = PasswordMetrics.computeForPassword(credential).quality; if (!getLockSettings().setLockCredentialWithToken(credential, type, tokenHandle, token, userId)) { return false; } setLong(PASSWORD_TYPE_KEY, Math.max(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, computedQuality), userId); updateEncryptionPasswordIfNeeded(credential, computedQuality, userId); updatePasswordHistory(credential, userId); } else { if (!TextUtils.isEmpty(credential)) { throw new IllegalArgumentException("password must be emtpy for NONE type"); } if (!getLockSettings().setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE, tokenHandle, token, userId)) { return false; } setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId); if (userId == UserHandle.USER_SYSTEM) { // Set the encryption password to default. updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null); setCredentialRequiredToDecrypt(false); } } onAfterChangingPassword(userId); return true; } catch (RemoteException re) { Log.e(TAG, "Unable to save lock password ", re); re.rethrowFromSystemServer(); } return false; } public void unlockUserWithToken(long tokenHandle, byte[] token, int userId) { try { getLockSettings().unlockUserWithToken(tokenHandle, token, userId); } catch (RemoteException re) { Log.e(TAG, "Unable to unlock user with token", re); re.rethrowFromSystemServer(); } } /** * Callback to be notified about progress when checking credentials. */ Loading services/core/java/com/android/server/LockSettingsService.java +164 −0 Original line number Diff line number Diff line Loading @@ -1926,6 +1926,7 @@ public class LockSettingsService extends ILockSettings.Stub { (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); trustManager.setDeviceLockedForUser(userId, false); } activateEscrowTokens(authResult.authToken, userId); } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { if (response.getTimeout() > 0) { requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); Loading Loading @@ -2024,6 +2025,132 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId); } notifyActivePasswordMetricsAvailable(credential, userId); } @Override public long addEscrowToken(byte[] token, int userId) throws RemoteException { ensureCallerSystemUid(); if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId); synchronized (mSpManager) { enableSyntheticPasswordLocked(); // Migrate to synthetic password based credentials if ther user has no password, // the token can then be activated immediately. AuthenticationToken auth = null; if (!isUserSecure(userId)) { if (shouldMigrateToSyntheticPasswordLocked(userId)) { auth = initializeSyntheticPasswordLocked(null, null, LockPatternUtils.CREDENTIAL_TYPE_NONE, userId); } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ { long pwdHandle = getSyntheticPasswordHandleLocked(userId); auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(), pwdHandle, null, userId).authToken; } } disableEscrowTokenOnNonManagedDevicesIfNeeded(userId); if (!mSpManager.hasEscrowData(userId)) { throw new SecurityException("Escrow token is disabled on the current user"); } long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId); if (auth != null) { mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId); } return handle; } } private void activateEscrowTokens(AuthenticationToken auth, int userId) throws RemoteException { if (DEBUG) Slog.d(TAG, "activateEscrowTokens: user=" + userId); synchronized (mSpManager) { for (long handle : mSpManager.getPendingTokensForUser(userId)) { Slog.i(TAG, String.format("activateEscrowTokens: %x %d ", handle, userId)); mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId); } } } @Override public boolean isEscrowTokenActive(long handle, int userId) throws RemoteException { ensureCallerSystemUid(); synchronized (mSpManager) { return mSpManager.existsHandle(handle, userId); } } @Override public boolean removeEscrowToken(long handle, int userId) throws RemoteException { ensureCallerSystemUid(); synchronized (mSpManager) { if (handle == getSyntheticPasswordHandleLocked(userId)) { Slog.w(TAG, "Cannot remove password handle"); return false; } if (mSpManager.removePendingToken(handle, userId)) { return true; } if (mSpManager.existsHandle(handle, userId)) { mSpManager.destroyTokenBasedSyntheticPassword(handle, userId); return true; } else { return false; } } } @Override public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, byte[] token, int userId) throws RemoteException { ensureCallerSystemUid(); boolean result; synchronized (mSpManager) { if (!mSpManager.hasEscrowData(userId)) { throw new SecurityException("Escrow token is disabled on the current user"); } result = setLockCredentialWithTokenInternal(credential, type, tokenHandle, token, userId); } if (result) { synchronized (mSeparateChallengeLock) { setSeparateProfileChallengeEnabled(userId, true, null); } notifyPasswordChanged(userId); } return result; } private boolean setLockCredentialWithTokenInternal(String credential, int type, long tokenHandle, byte[] token, int userId) throws RemoteException { synchronized (mSpManager) { AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword( getGateKeeperService(), tokenHandle, token, userId); if (result.authToken == null) { Slog.w(TAG, "Invalid escrow token supplied"); return false; } long oldHandle = getSyntheticPasswordHandleLocked(userId); setLockCredentialWithAuthTokenLocked(credential, type, result.authToken, userId); mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId); return true; } } @Override public void unlockUserWithToken(long tokenHandle, byte[] token, int userId) throws RemoteException { ensureCallerSystemUid(); AuthenticationResult authResult; synchronized (mSpManager) { if (!mSpManager.hasEscrowData(userId)) { throw new SecurityException("Escrow token is disabled on the current user"); } authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), tokenHandle, token, userId); if (authResult.authToken == null) { Slog.w(TAG, "Invalid escrow token supplied"); return; } } unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey()); } @Override Loading Loading @@ -2058,4 +2185,41 @@ public class LockSettingsService extends ILockSettings.Stub { } } private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) { long ident = Binder.clearCallingIdentity(); try { // Managed profile should have escrow enabled if (mUserManager.getUserInfo(userId).isManagedProfile()) { return; } DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); // Devices with Device Owner should have escrow enabled on all users. if (dpm.getDeviceOwnerComponentOnAnyUser() != null) { return; } // If the device is yet to be provisioned (still in SUW), there is still // a chance that Device Owner will be set on the device later, so postpone // disabling escrow token for now. if (!dpm.isDeviceProvisioned()) { return; } // Disable escrow token permanently on all other device/user types. Slog.i(TAG, "Disabling escrow token on user " + userId); if (isSyntheticPasswordBasedCredentialLocked(userId)) { mSpManager.destroyEscrowData(userId); } } catch (RemoteException e) { Slog.e(TAG, "disableEscrowTokenOnNonManagedDevices", e); } finally { Binder.restoreCallingIdentity(ident); } } private void ensureCallerSystemUid() throws SecurityException { final int callingUid = mInjector.binderGetCallingUid(); if (callingUid != Process.SYSTEM_UID) { throw new SecurityException("Only system can call this API."); } } } services/core/java/com/android/server/SyntheticPasswordManager.java +103 −3 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ public class SyntheticPasswordManager { private static final byte SYNTHETIC_PASSWORD_VERSION = 1; private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0; private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1; // 256-bit synthetic password private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8; Loading Loading @@ -113,7 +114,7 @@ public class SyntheticPasswordManager { syntheticPassword.getBytes()); } public void initialize(byte[] P0, byte[] P1) { private void initialize(byte[] P0, byte[] P1) { this.P1 = P1; this.syntheticPassword = String.valueOf(HexEncoding.encode( SyntheticPasswordCrypto.personalisedHash( Loading @@ -122,6 +123,10 @@ public class SyntheticPasswordManager { PERSONALIZATION_E0, P0); } public void recreate(byte[] secret) { initialize(secret, this.P1); } protected static AuthenticationToken create() { AuthenticationToken result = new AuthenticationToken(); result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH), Loading Loading @@ -293,6 +298,11 @@ public class SyntheticPasswordManager { saveState(SP_P1_NAME, authToken.P1, DEFAULT_HANDLE, userId); } public boolean hasEscrowData(int userId) { return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId) && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId); } public void destroyEscrowData(int userId) { destroyState(SP_E0_NAME, true, DEFAULT_HANDLE, userId); destroyState(SP_P1_NAME, true, DEFAULT_HANDLE, userId); Loading Loading @@ -339,9 +349,60 @@ public class SyntheticPasswordManager { return handle; } private ArrayMap<Integer, ArrayMap<Long, byte[]>> tokenMap = new ArrayMap<>(); public long createTokenBasedSyntheticPassword(byte[] token, int userId) { long handle = generateHandle(); byte[] applicationId = transformUnderSecdiscardable(token, createSecdiscardable(handle, userId)); if (!tokenMap.containsKey(userId)) { tokenMap.put(userId, new ArrayMap<>()); } tokenMap.get(userId).put(handle, applicationId); return handle; } public Set<Long> getPendingTokensForUser(int userId) { if (!tokenMap.containsKey(userId)) { return Collections.emptySet(); } return tokenMap.get(userId).keySet(); } public boolean removePendingToken(long handle, int userId) { if (!tokenMap.containsKey(userId)) { return false; } return tokenMap.get(userId).remove(handle) != null; } public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken, int userId) { if (!tokenMap.containsKey(userId)) { return false; } byte[] applicationId = tokenMap.get(userId).get(handle); if (applicationId == null) { return false; } if (!loadEscrowData(authToken, userId)) { Log.w(TAG, "User is not escrowable"); return false; } createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken, applicationId, 0L, userId); tokenMap.get(userId).remove(handle); return true; } private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken, byte[] applicationId, long sid, int userId) { final byte[] secret = authToken.syntheticPassword.getBytes(); final byte[] secret; if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { secret = authToken.computeP0(); } else { secret = authToken.syntheticPassword.getBytes(); } byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid); byte[] blob = new byte[content.length + 1 + 1]; blob[0] = SYNTHETIC_PASSWORD_VERSION; Loading Loading @@ -400,6 +461,32 @@ public class SyntheticPasswordManager { return result; } /** * Decrypt a synthetic password by supplying an escrow token and corresponding token * blob handle generated previously. If the decryption is successful, initiate a GateKeeper * verification to referesh the SID & Auth token maintained by the system. */ public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword( IGateKeeperService gatekeeper, long handle, byte[] token, int userId) throws RemoteException { AuthenticationResult result = new AuthenticationResult(); byte[] applicationId = transformUnderSecdiscardable(token, loadSecdiscardable(handle, userId)); result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, applicationId, userId); if (result.authToken != null) { result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId); if (result.gkResponse == null) { // The user currently has no password. return OK with null payload so null // is propagated to unlockUser() result.gkResponse = VerifyCredentialResponse.OK; } } else { result.gkResponse = VerifyCredentialResponse.ERROR; } return result; } private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type, byte[] applicationId, int userId) { byte[] blob = loadState(SP_BLOB_NAME, handle, userId); Loading @@ -419,7 +506,15 @@ public class SyntheticPasswordManager { return null; } AuthenticationToken result = new AuthenticationToken(); if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { if (!loadEscrowData(result, userId)) { Log.e(TAG, "User is not escrowable: " + userId); return null; } result.recreate(secret); } else { result.syntheticPassword = new String(secret); } return result; } Loading Loading @@ -470,6 +565,11 @@ public class SyntheticPasswordManager { return hasState(SP_BLOB_NAME, handle, userId); } public void destroyTokenBasedSyntheticPassword(long handle, int userId) { destroySyntheticPassword(handle, userId); destroyState(SECDISCARDABLE_NAME, true, handle, userId); } public void destroyPasswordBasedSyntheticPassword(long handle, int userId) { destroySyntheticPassword(handle, userId); destroyState(SECDISCARDABLE_NAME, true, handle, userId); Loading services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java +83 −0 Original line number Diff line number Diff line Loading @@ -237,6 +237,89 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID)); } public void testTokenBasedResetPassword() throws RemoteException { final String PASSWORD = "password"; final String PATTERN = "123654"; final String TOKEN = "some-high-entropy-secure-token"; initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode()); assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); } public void testTokenBasedClearPassword() throws RemoteException { final String PASSWORD = "password"; final String PATTERN = "123654"; final String TOKEN = "some-high-entropy-secure-token"; initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID); mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode()); assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); } public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException { final String PASSWORD = "password"; final String PATTERN = "123654"; final String NEWPASSWORD = "password"; final String TOKEN = "some-high-entropy-secure-token"; initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD, PRIMARY_USER_ID); mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); } public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration() throws RemoteException { final String TOKEN = "some-high-entropy-secure-token"; enableSyntheticPassword(PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); } public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration() throws RemoteException { final String TOKEN = "some-high-entropy-secure-token"; initializeCredentialUnderSP(null, PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); } // b/34600579 //TODO: add non-migration work profile case, and unify/un-unify transition. //TODO: test token after user resets password Loading Loading
core/java/com/android/internal/widget/ILockSettings.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -45,4 +45,10 @@ interface ILockSettings { void systemReady(); void userPresent(int userId); int getStrongAuthForUser(int userId); long addEscrowToken(in byte[] token, int userId); boolean removeEscrowToken(long handle, int userId); boolean isEscrowTokenActive(long handle, int userId); boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, in byte[] token, int userId); void unlockUserWithToken(long tokenHandle, in byte[] token, int userId); }
core/java/com/android/internal/widget/LockPatternUtils.java +104 −2 Original line number Diff line number Diff line Loading @@ -773,7 +773,7 @@ public class LockPatternUtils { getLockSettings().setLockCredential(password, CREDENTIAL_TYPE_PASSWORD, savedPassword, userHandle); addEncryptionPassword(password, computedQuality, userHandle); updateEncryptionPasswordIfNeeded(password, computedQuality, userHandle); updatePasswordHistory(password, userHandle); } catch (RemoteException re) { // Cant do much Loading @@ -781,7 +781,11 @@ public class LockPatternUtils { } } private void addEncryptionPassword(String password, int quality, int userHandle) { /** * Update device encryption password if calling user is USER_SYSTEM and device supports * encryption. */ private void updateEncryptionPasswordIfNeeded(String password, int quality, int userHandle) { // Update the device encryption password. if (userHandle == UserHandle.USER_SYSTEM && LockPatternUtils.isDeviceEncryptionEnabled()) { Loading Loading @@ -1401,6 +1405,104 @@ public class LockPatternUtils { } } /** * Create an escrow token for the current user, which can later be used to unlock FBE * or change user password. * * After adding, if the user currently has lockscreen password, he will need to perform a * confirm credential operation in order to activate the token for future use. If the user * has no secure lockscreen, then the token is activated immediately. * * @return a unique 64-bit token handle which is needed to refer to this token later. */ public long addEscrowToken(byte[] token, int userId) { try { return getLockSettings().addEscrowToken(token, userId); } catch (RemoteException re) { return 0L; } } /** * Remove an escrow token. * @return true if the given handle refers to a valid token previously returned from * {@link #addEscrowToken}, whether it's active or not. return false otherwise. */ public boolean removeEscrowToken(long handle, int userId) { try { return getLockSettings().removeEscrowToken(handle, userId); } catch (RemoteException re) { return false; } } /** * Check if the given escrow token is active or not. Only active token can be used to call * {@link #setLockCredentialWithToken} and {@link #unlockUserWithToken} */ public boolean isEscrowTokenActive(long handle, int userId) { try { return getLockSettings().isEscrowTokenActive(handle, userId); } catch (RemoteException re) { return false; } } public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, byte[] token, int userId) { try { if (type != CREDENTIAL_TYPE_NONE) { if (TextUtils.isEmpty(credential) || credential.length() < MIN_LOCK_PASSWORD_SIZE) { throw new IllegalArgumentException("password must not be null and at least " + "of length " + MIN_LOCK_PASSWORD_SIZE); } final int computedQuality = PasswordMetrics.computeForPassword(credential).quality; if (!getLockSettings().setLockCredentialWithToken(credential, type, tokenHandle, token, userId)) { return false; } setLong(PASSWORD_TYPE_KEY, Math.max(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, computedQuality), userId); updateEncryptionPasswordIfNeeded(credential, computedQuality, userId); updatePasswordHistory(credential, userId); } else { if (!TextUtils.isEmpty(credential)) { throw new IllegalArgumentException("password must be emtpy for NONE type"); } if (!getLockSettings().setLockCredentialWithToken(null, CREDENTIAL_TYPE_NONE, tokenHandle, token, userId)) { return false; } setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userId); if (userId == UserHandle.USER_SYSTEM) { // Set the encryption password to default. updateEncryptionPassword(StorageManager.CRYPT_TYPE_DEFAULT, null); setCredentialRequiredToDecrypt(false); } } onAfterChangingPassword(userId); return true; } catch (RemoteException re) { Log.e(TAG, "Unable to save lock password ", re); re.rethrowFromSystemServer(); } return false; } public void unlockUserWithToken(long tokenHandle, byte[] token, int userId) { try { getLockSettings().unlockUserWithToken(tokenHandle, token, userId); } catch (RemoteException re) { Log.e(TAG, "Unable to unlock user with token", re); re.rethrowFromSystemServer(); } } /** * Callback to be notified about progress when checking credentials. */ Loading
services/core/java/com/android/server/LockSettingsService.java +164 −0 Original line number Diff line number Diff line Loading @@ -1926,6 +1926,7 @@ public class LockSettingsService extends ILockSettings.Stub { (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); trustManager.setDeviceLockedForUser(userId, false); } activateEscrowTokens(authResult.authToken, userId); } else if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_RETRY) { if (response.getTimeout() > 0) { requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_LOCKOUT, userId); Loading Loading @@ -2024,6 +2025,132 @@ public class LockSettingsService extends ILockSettings.Stub { mSpManager.destroyPasswordBasedSyntheticPassword(handle, userId); } notifyActivePasswordMetricsAvailable(credential, userId); } @Override public long addEscrowToken(byte[] token, int userId) throws RemoteException { ensureCallerSystemUid(); if (DEBUG) Slog.d(TAG, "addEscrowToken: user=" + userId); synchronized (mSpManager) { enableSyntheticPasswordLocked(); // Migrate to synthetic password based credentials if ther user has no password, // the token can then be activated immediately. AuthenticationToken auth = null; if (!isUserSecure(userId)) { if (shouldMigrateToSyntheticPasswordLocked(userId)) { auth = initializeSyntheticPasswordLocked(null, null, LockPatternUtils.CREDENTIAL_TYPE_NONE, userId); } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ { long pwdHandle = getSyntheticPasswordHandleLocked(userId); auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(), pwdHandle, null, userId).authToken; } } disableEscrowTokenOnNonManagedDevicesIfNeeded(userId); if (!mSpManager.hasEscrowData(userId)) { throw new SecurityException("Escrow token is disabled on the current user"); } long handle = mSpManager.createTokenBasedSyntheticPassword(token, userId); if (auth != null) { mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId); } return handle; } } private void activateEscrowTokens(AuthenticationToken auth, int userId) throws RemoteException { if (DEBUG) Slog.d(TAG, "activateEscrowTokens: user=" + userId); synchronized (mSpManager) { for (long handle : mSpManager.getPendingTokensForUser(userId)) { Slog.i(TAG, String.format("activateEscrowTokens: %x %d ", handle, userId)); mSpManager.activateTokenBasedSyntheticPassword(handle, auth, userId); } } } @Override public boolean isEscrowTokenActive(long handle, int userId) throws RemoteException { ensureCallerSystemUid(); synchronized (mSpManager) { return mSpManager.existsHandle(handle, userId); } } @Override public boolean removeEscrowToken(long handle, int userId) throws RemoteException { ensureCallerSystemUid(); synchronized (mSpManager) { if (handle == getSyntheticPasswordHandleLocked(userId)) { Slog.w(TAG, "Cannot remove password handle"); return false; } if (mSpManager.removePendingToken(handle, userId)) { return true; } if (mSpManager.existsHandle(handle, userId)) { mSpManager.destroyTokenBasedSyntheticPassword(handle, userId); return true; } else { return false; } } } @Override public boolean setLockCredentialWithToken(String credential, int type, long tokenHandle, byte[] token, int userId) throws RemoteException { ensureCallerSystemUid(); boolean result; synchronized (mSpManager) { if (!mSpManager.hasEscrowData(userId)) { throw new SecurityException("Escrow token is disabled on the current user"); } result = setLockCredentialWithTokenInternal(credential, type, tokenHandle, token, userId); } if (result) { synchronized (mSeparateChallengeLock) { setSeparateProfileChallengeEnabled(userId, true, null); } notifyPasswordChanged(userId); } return result; } private boolean setLockCredentialWithTokenInternal(String credential, int type, long tokenHandle, byte[] token, int userId) throws RemoteException { synchronized (mSpManager) { AuthenticationResult result = mSpManager.unwrapTokenBasedSyntheticPassword( getGateKeeperService(), tokenHandle, token, userId); if (result.authToken == null) { Slog.w(TAG, "Invalid escrow token supplied"); return false; } long oldHandle = getSyntheticPasswordHandleLocked(userId); setLockCredentialWithAuthTokenLocked(credential, type, result.authToken, userId); mSpManager.destroyPasswordBasedSyntheticPassword(oldHandle, userId); return true; } } @Override public void unlockUserWithToken(long tokenHandle, byte[] token, int userId) throws RemoteException { ensureCallerSystemUid(); AuthenticationResult authResult; synchronized (mSpManager) { if (!mSpManager.hasEscrowData(userId)) { throw new SecurityException("Escrow token is disabled on the current user"); } authResult = mSpManager.unwrapTokenBasedSyntheticPassword(getGateKeeperService(), tokenHandle, token, userId); if (authResult.authToken == null) { Slog.w(TAG, "Invalid escrow token supplied"); return; } } unlockUser(userId, null, authResult.authToken.deriveDiskEncryptionKey()); } @Override Loading Loading @@ -2058,4 +2185,41 @@ public class LockSettingsService extends ILockSettings.Stub { } } private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) { long ident = Binder.clearCallingIdentity(); try { // Managed profile should have escrow enabled if (mUserManager.getUserInfo(userId).isManagedProfile()) { return; } DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); // Devices with Device Owner should have escrow enabled on all users. if (dpm.getDeviceOwnerComponentOnAnyUser() != null) { return; } // If the device is yet to be provisioned (still in SUW), there is still // a chance that Device Owner will be set on the device later, so postpone // disabling escrow token for now. if (!dpm.isDeviceProvisioned()) { return; } // Disable escrow token permanently on all other device/user types. Slog.i(TAG, "Disabling escrow token on user " + userId); if (isSyntheticPasswordBasedCredentialLocked(userId)) { mSpManager.destroyEscrowData(userId); } } catch (RemoteException e) { Slog.e(TAG, "disableEscrowTokenOnNonManagedDevices", e); } finally { Binder.restoreCallingIdentity(ident); } } private void ensureCallerSystemUid() throws SecurityException { final int callingUid = mInjector.binderGetCallingUid(); if (callingUid != Process.SYSTEM_UID) { throw new SecurityException("Only system can call this API."); } } }
services/core/java/com/android/server/SyntheticPasswordManager.java +103 −3 Original line number Diff line number Diff line Loading @@ -63,6 +63,7 @@ public class SyntheticPasswordManager { private static final byte SYNTHETIC_PASSWORD_VERSION = 1; private static final byte SYNTHETIC_PASSWORD_PASSWORD_BASED = 0; private static final byte SYNTHETIC_PASSWORD_TOKEN_BASED = 1; // 256-bit synthetic password private static final byte SYNTHETIC_PASSWORD_LENGTH = 256 / 8; Loading Loading @@ -113,7 +114,7 @@ public class SyntheticPasswordManager { syntheticPassword.getBytes()); } public void initialize(byte[] P0, byte[] P1) { private void initialize(byte[] P0, byte[] P1) { this.P1 = P1; this.syntheticPassword = String.valueOf(HexEncoding.encode( SyntheticPasswordCrypto.personalisedHash( Loading @@ -122,6 +123,10 @@ public class SyntheticPasswordManager { PERSONALIZATION_E0, P0); } public void recreate(byte[] secret) { initialize(secret, this.P1); } protected static AuthenticationToken create() { AuthenticationToken result = new AuthenticationToken(); result.initialize(secureRandom(SYNTHETIC_PASSWORD_LENGTH), Loading Loading @@ -293,6 +298,11 @@ public class SyntheticPasswordManager { saveState(SP_P1_NAME, authToken.P1, DEFAULT_HANDLE, userId); } public boolean hasEscrowData(int userId) { return hasState(SP_E0_NAME, DEFAULT_HANDLE, userId) && hasState(SP_P1_NAME, DEFAULT_HANDLE, userId); } public void destroyEscrowData(int userId) { destroyState(SP_E0_NAME, true, DEFAULT_HANDLE, userId); destroyState(SP_P1_NAME, true, DEFAULT_HANDLE, userId); Loading Loading @@ -339,9 +349,60 @@ public class SyntheticPasswordManager { return handle; } private ArrayMap<Integer, ArrayMap<Long, byte[]>> tokenMap = new ArrayMap<>(); public long createTokenBasedSyntheticPassword(byte[] token, int userId) { long handle = generateHandle(); byte[] applicationId = transformUnderSecdiscardable(token, createSecdiscardable(handle, userId)); if (!tokenMap.containsKey(userId)) { tokenMap.put(userId, new ArrayMap<>()); } tokenMap.get(userId).put(handle, applicationId); return handle; } public Set<Long> getPendingTokensForUser(int userId) { if (!tokenMap.containsKey(userId)) { return Collections.emptySet(); } return tokenMap.get(userId).keySet(); } public boolean removePendingToken(long handle, int userId) { if (!tokenMap.containsKey(userId)) { return false; } return tokenMap.get(userId).remove(handle) != null; } public boolean activateTokenBasedSyntheticPassword(long handle, AuthenticationToken authToken, int userId) { if (!tokenMap.containsKey(userId)) { return false; } byte[] applicationId = tokenMap.get(userId).get(handle); if (applicationId == null) { return false; } if (!loadEscrowData(authToken, userId)) { Log.w(TAG, "User is not escrowable"); return false; } createSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, authToken, applicationId, 0L, userId); tokenMap.get(userId).remove(handle); return true; } private void createSyntheticPasswordBlob(long handle, byte type, AuthenticationToken authToken, byte[] applicationId, long sid, int userId) { final byte[] secret = authToken.syntheticPassword.getBytes(); final byte[] secret; if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { secret = authToken.computeP0(); } else { secret = authToken.syntheticPassword.getBytes(); } byte[] content = createSPBlob(getHandleName(handle), secret, applicationId, sid); byte[] blob = new byte[content.length + 1 + 1]; blob[0] = SYNTHETIC_PASSWORD_VERSION; Loading Loading @@ -400,6 +461,32 @@ public class SyntheticPasswordManager { return result; } /** * Decrypt a synthetic password by supplying an escrow token and corresponding token * blob handle generated previously. If the decryption is successful, initiate a GateKeeper * verification to referesh the SID & Auth token maintained by the system. */ public @NonNull AuthenticationResult unwrapTokenBasedSyntheticPassword( IGateKeeperService gatekeeper, long handle, byte[] token, int userId) throws RemoteException { AuthenticationResult result = new AuthenticationResult(); byte[] applicationId = transformUnderSecdiscardable(token, loadSecdiscardable(handle, userId)); result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_TOKEN_BASED, applicationId, userId); if (result.authToken != null) { result.gkResponse = verifyChallenge(gatekeeper, result.authToken, 0L, userId); if (result.gkResponse == null) { // The user currently has no password. return OK with null payload so null // is propagated to unlockUser() result.gkResponse = VerifyCredentialResponse.OK; } } else { result.gkResponse = VerifyCredentialResponse.ERROR; } return result; } private AuthenticationToken unwrapSyntheticPasswordBlob(long handle, byte type, byte[] applicationId, int userId) { byte[] blob = loadState(SP_BLOB_NAME, handle, userId); Loading @@ -419,7 +506,15 @@ public class SyntheticPasswordManager { return null; } AuthenticationToken result = new AuthenticationToken(); if (type == SYNTHETIC_PASSWORD_TOKEN_BASED) { if (!loadEscrowData(result, userId)) { Log.e(TAG, "User is not escrowable: " + userId); return null; } result.recreate(secret); } else { result.syntheticPassword = new String(secret); } return result; } Loading Loading @@ -470,6 +565,11 @@ public class SyntheticPasswordManager { return hasState(SP_BLOB_NAME, handle, userId); } public void destroyTokenBasedSyntheticPassword(long handle, int userId) { destroySyntheticPassword(handle, userId); destroyState(SECDISCARDABLE_NAME, true, handle, userId); } public void destroyPasswordBasedSyntheticPassword(long handle, int userId) { destroySyntheticPassword(handle, userId); destroyState(SECDISCARDABLE_NAME, true, handle, userId); Loading
services/tests/servicestests/src/com/android/server/SyntheticPasswordTests.java +83 −0 Original line number Diff line number Diff line Loading @@ -237,6 +237,89 @@ public class SyntheticPasswordTests extends BaseLockSettingsServiceTests { assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); assertTrue(hasSyntheticPassword(MANAGED_PROFILE_USER_ID)); } public void testTokenBasedResetPassword() throws RemoteException { final String PASSWORD = "password"; final String PATTERN = "123654"; final String TOKEN = "some-high-entropy-secure-token"; initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, TOKEN.getBytes(), PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode()); assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); } public void testTokenBasedClearPassword() throws RemoteException { final String PASSWORD = "password"; final String PATTERN = "123654"; final String TOKEN = "some-high-entropy-secure-token"; initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.setLockCredentialWithToken(null, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID); mService.setLockCredentialWithToken(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, 0, PRIMARY_USER_ID).getResponseCode()); assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); } public void testTokenBasedResetPasswordAfterCredentialChanges() throws RemoteException { final String PASSWORD = "password"; final String PATTERN = "123654"; final String NEWPASSWORD = "password"; final String TOKEN = "some-high-entropy-secure-token"; initializeCredentialUnderSP(PASSWORD, PRIMARY_USER_ID); final byte[] storageKey = mStorageManager.getUserUnlockToken(PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertFalse(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.verifyCredential(PASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode(); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); mService.setLockCredential(PATTERN, LockPatternUtils.CREDENTIAL_TYPE_PATTERN, PASSWORD, PRIMARY_USER_ID); mService.setLockCredentialWithToken(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, TOKEN.getBytes(), PRIMARY_USER_ID); assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(NEWPASSWORD, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, 0, PRIMARY_USER_ID).getResponseCode()); assertArrayEquals(storageKey, mStorageManager.getUserUnlockToken(PRIMARY_USER_ID)); } public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNeedsMigration() throws RemoteException { final String TOKEN = "some-high-entropy-secure-token"; enableSyntheticPassword(PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); } public void testEscrowTokenActivatedImmediatelyIfNoUserPasswordNoMigration() throws RemoteException { final String TOKEN = "some-high-entropy-secure-token"; initializeCredentialUnderSP(null, PRIMARY_USER_ID); long handle = mService.addEscrowToken(TOKEN.getBytes(), PRIMARY_USER_ID); assertTrue(mService.isEscrowTokenActive(handle, PRIMARY_USER_ID)); assertEquals(0, mGateKeeperService.getSecureUserId(PRIMARY_USER_ID)); assertTrue(hasSyntheticPassword(PRIMARY_USER_ID)); } // b/34600579 //TODO: add non-migration work profile case, and unify/un-unify transition. //TODO: test token after user resets password Loading