Loading services/core/java/com/android/server/locksettings/LockSettingsService.java +13 −108 Original line number Diff line number Diff line Loading @@ -47,6 +47,11 @@ import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityT import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential; import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG; import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.decryptProfilePassword; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.encryptProfilePassword; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.profilePasswordDecryptAlias; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.profilePasswordEncryptAlias; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.removeKeystoreProfileKey; import android.Manifest; import android.annotation.NonNull; Loading Loading @@ -103,12 +108,10 @@ import android.provider.Settings; import android.security.AndroidKeyStoreMaintenance; import android.security.KeyStoreAuthorization; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; import android.security.keystore.recovery.RecoveryCertPath; import android.security.keystore.recovery.WrappedApplicationKey; import android.security.keystore2.AndroidKeyStoreLoadStoreParameter; import android.security.keystore2.AndroidKeyStoreProvider; import android.service.gatekeeper.IGateKeeperService; import android.service.notification.StatusBarNotification; Loading Loading @@ -163,13 +166,11 @@ import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.text.SimpleDateFormat; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Enumeration; import java.util.List; Loading @@ -183,12 +184,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; /** * LockSettingsService (LSS) mainly has the following responsibilities: Loading Loading @@ -231,7 +228,6 @@ import javax.crypto.spec.GCMParameterSpec; public class LockSettingsService extends ILockSettings.Stub { private static final String TAG = "LockSettingsService"; private static final int PROFILE_KEY_IV_SIZE = 12; private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge"; private static final String PREV_LSKF_BASED_PROTECTOR_ID_KEY = "prev-sp-handle"; private static final String LSKF_LAST_CHANGED_TIME_KEY = "sp-handle-ts"; Loading @@ -252,9 +248,6 @@ public class LockSettingsService extends ILockSettings.Stub { // user's credential must be presented again, e.g. via ConfirmLockPattern/ConfirmLockPassword. private static final int GK_PW_HANDLE_STORE_DURATION_MS = 10 * 60 * 1000; // 10 minutes private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_"; private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_"; private static final int HEADLESS_VENDOR_AUTH_SECRET_LENGTH = 32; // Order of holding lock: Loading Loading @@ -624,11 +617,7 @@ public class LockSettingsService extends ILockSettings.Stub { public KeyStore getKeyStore() { try { KeyStore ks = KeyStore.getInstance( SyntheticPasswordCrypto.androidKeystoreProviderName()); ks.load(new AndroidKeyStoreLoadStoreParameter( SyntheticPasswordCrypto.keyNamespace())); return ks; return SyntheticPasswordCrypto.getKeyStore(); } catch (Exception e) { throw new IllegalStateException("Cannot load keystore", e); } Loading Loading @@ -1359,7 +1348,7 @@ public class LockSettingsService extends ILockSettings.Stub { try { if (enabled) { mStorage.removeChildProfileLock(userId); removeKeystoreProfileKey(userId); removeKeystoreProfileKey(mKeyStore, userId); } else { synchronized (mSpManager) { tieProfileLockIfNecessary(userId, profileUserPassword); Loading Loading @@ -1551,21 +1540,7 @@ public class LockSettingsService extends ILockSettings.Stub { if (storedData == null) { throw new FileNotFoundException("Child profile lock file not found"); } byte[] iv = Arrays.copyOfRange(storedData, 0, PROFILE_KEY_IV_SIZE); byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE, storedData.length); byte[] decryptionResult; SecretKey decryptionKey = (SecretKey) mKeyStore.getKey( profilePasswordDecryptAlias(userId), null); Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv)); decryptionResult = cipher.doFinal(encryptedPassword); LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword( decryptionResult); ArrayUtils.zeroize(decryptionResult); LockscreenCredential credential = decryptProfilePassword(mKeyStore, userId, storedData); try { long parentSid = getGateKeeperService().getSecureUserId( mUserManager.getProfileParent(userId).id); Loading Loading @@ -1749,7 +1724,7 @@ public class LockSettingsService extends ILockSettings.Stub { profileUserId, /* isLockTiedToParent= */ true); mStorage.removeChildProfileLock(profileUserId); removeKeystoreProfileKey(profileUserId); removeKeystoreProfileKey(mKeyStore, profileUserId); } else { Slog.wtf(TAG, "Attempt to clear tied challenge, but no password supplied."); } Loading Loading @@ -2168,60 +2143,15 @@ public class LockSettingsService extends ILockSettings.Stub { LockscreenCredential password) { Slogf.i(TAG, "Tying lock for profile user %d to parent user %d", profileUserId, parentUserId); final byte[] iv; final byte[] ciphertext; final long parentSid; try { parentSid = getGateKeeperService().getSecureUserId(parentUserId); } catch (RemoteException e) { throw new IllegalStateException("Failed to talk to GateKeeper service", e); } try { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); keyGenerator.init(new SecureRandom()); SecretKey secretKey = keyGenerator.generateKey(); try { mKeyStore.setEntry( profilePasswordEncryptAlias(profileUserId), new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); mKeyStore.setEntry( profilePasswordDecryptAlias(profileUserId), new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) .setBoundToSpecificSecureUserId(parentSid) .setUserAuthenticationValidityDurationSeconds(30) .build()); // Key imported, obtain a reference to it. SecretKey keyStoreEncryptionKey = (SecretKey) mKeyStore.getKey( profilePasswordEncryptAlias(profileUserId), null); Cipher cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey); ciphertext = cipher.doFinal(password.getCredential()); iv = cipher.getIV(); } finally { // The original key can now be discarded. mKeyStore.deleteEntry(profilePasswordEncryptAlias(profileUserId)); } } catch (UnrecoverableKeyException | BadPaddingException | IllegalBlockSizeException | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { throw new IllegalStateException("Failed to encrypt key", e); } if (iv.length != PROFILE_KEY_IV_SIZE) { throw new IllegalArgumentException("Invalid iv length: " + iv.length); } mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext)); byte[] encryptedPasswordData = encryptProfilePassword(mKeyStore, profileUserId, parentSid, password); mStorage.writeChildProfileLock(profileUserId, encryptedPasswordData); } private void setCeStorageProtection(@UserIdInt int userId, SyntheticPassword sp) { Loading Loading @@ -2708,37 +2638,12 @@ public class LockSettingsService extends ILockSettings.Stub { mUnifiedProfilePasswordCache.removePassword(userId); gateKeeperClearSecureUserId(userId); removeKeystoreProfileKey(userId); removeKeystoreProfileKey(mKeyStore, userId); // Clean up storage last, so that removeStateForReusedUserIdIfNecessary() can assume that no // USER_SERIAL_NUMBER_KEY means user is fully removed. mStorage.removeUser(userId); } // TODO: b/412331826 Add protectorId param private static String profilePasswordEncryptAlias(int profileUserId) { return PROFILE_KEY_NAME_ENCRYPT + profileUserId; } // TODO: b/412331826 Add protectorId param private static String profilePasswordDecryptAlias(int profileUserId) { return PROFILE_KEY_NAME_DECRYPT + profileUserId; } private void removeKeystoreProfileKey(int targetUserId) { final String encryptAlias = profilePasswordEncryptAlias(targetUserId); final String decryptAlias = profilePasswordDecryptAlias(targetUserId); try { if (mKeyStore.containsAlias(encryptAlias) || mKeyStore.containsAlias(decryptAlias)) { Slogf.i(TAG, "Removing keystore profile key for user %d", targetUserId); mKeyStore.deleteEntry(encryptAlias); mKeyStore.deleteEntry(decryptAlias); } } catch (KeyStoreException e) { // We have tried our best to remove the key. Slogf.e(TAG, e, "Error removing keystore profile key for user %d", targetUserId); } } @Override public void registerStrongAuthTracker(IStrongAuthTracker tracker) { checkPasswordReadPermission(); Loading services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java +1 −1 Original line number Diff line number Diff line Loading @@ -155,7 +155,7 @@ class SyntheticPasswordCrypto { return KeyProperties.NAMESPACE_LOCKSETTINGS; } private static KeyStore getKeyStore() static KeyStore getKeyStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName()); keyStore.load(new AndroidKeyStoreLoadStoreParameter(keyNamespace())); Loading services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCrypto.java 0 → 100644 +169 −0 Original line number Diff line number Diff line /* * Copyright 2025 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.server.locksettings; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockscreenCredential; import com.android.server.utils.Slogf; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; /** * Helpers for the cryptography related to managing unified passwords for child profiles. */ class UnifiedProfilePasswordCrypto { private static final String TAG = "UnifiedProfilePasswordCrypto"; private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_"; private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_"; private static final int PROFILE_KEY_IV_SIZE = 12; private UnifiedProfilePasswordCrypto() { } /** * Decrypts the given byte array using the parent-bound key into a {@link LockscreenCredential}. * The input format is expected to match the output of the * {@link #encryptProfilePassword(KeyStore, int, long, LockscreenCredential)} method. */ static LockscreenCredential decryptProfilePassword(KeyStore keyStore, int userId, byte[] storedData) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnrecoverableKeyException, KeyStoreException { byte[] iv = Arrays.copyOfRange(storedData, 0, PROFILE_KEY_IV_SIZE); byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE, storedData.length); byte[] decryptionResult; SecretKey decryptionKey = (SecretKey) keyStore.getKey( profilePasswordDecryptAlias(userId), null); Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv)); decryptionResult = cipher.doFinal(encryptedPassword); LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword( decryptionResult); ArrayUtils.zeroize(decryptionResult); return credential; } /** * Creates a parent-bound key and encrypts the given password with it. The result is a byte * array as follows: * * <pre> * <- PROFILE_KEY_IV_SIZE -> * [ iv , ciphertext ] * </pre> */ static byte[] encryptProfilePassword(KeyStore keyStore, int profileUserId, long parentSid, LockscreenCredential password) { final byte[] iv; final byte[] ciphertext; try { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); keyGenerator.init(new SecureRandom()); SecretKey secretKey = keyGenerator.generateKey(); try { keyStore.setEntry( profilePasswordEncryptAlias(profileUserId), new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); keyStore.setEntry( profilePasswordDecryptAlias(profileUserId), new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) .setBoundToSpecificSecureUserId(parentSid) .setUserAuthenticationValidityDurationSeconds(30) .build()); // Key imported, obtain a reference to it. SecretKey keyStoreEncryptionKey = (SecretKey) keyStore.getKey( profilePasswordEncryptAlias(profileUserId), null); Cipher cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey); ciphertext = cipher.doFinal(password.getCredential()); iv = cipher.getIV(); } finally { // The original key can now be discarded. keyStore.deleteEntry(profilePasswordEncryptAlias(profileUserId)); } } catch (UnrecoverableKeyException | BadPaddingException | IllegalBlockSizeException | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { throw new IllegalStateException("Failed to encrypt key", e); } if (iv.length != PROFILE_KEY_IV_SIZE) { throw new IllegalArgumentException("Invalid iv length: " + iv.length); } return ArrayUtils.concat(iv, ciphertext); } // TODO: b/412331826 Add protectorId param static String profilePasswordEncryptAlias(int profileUserId) { return PROFILE_KEY_NAME_ENCRYPT + profileUserId; } // TODO: b/412331826 Add protectorId param static String profilePasswordDecryptAlias(int profileUserId) { return PROFILE_KEY_NAME_DECRYPT + profileUserId; } /** Cleans up the keystore entries for the profile password's encrypt/decrypt keys. */ static void removeKeystoreProfileKey(KeyStore keyStore, int targetUserId) { final String encryptAlias = profilePasswordEncryptAlias(targetUserId); final String decryptAlias = profilePasswordDecryptAlias(targetUserId); try { if (keyStore.containsAlias(encryptAlias) || keyStore.containsAlias(decryptAlias)) { Slogf.i(TAG, "Removing keystore profile key for user %d", targetUserId); keyStore.deleteEntry(encryptAlias); keyStore.deleteEntry(decryptAlias); } } catch (KeyStoreException e) { // We have tried our best to remove the key. Slogf.e(TAG, e, "Error removing keystore profile key for user %d", targetUserId); } } } Loading
services/core/java/com/android/server/locksettings/LockSettingsService.java +13 −108 Original line number Diff line number Diff line Loading @@ -47,6 +47,11 @@ import static com.android.internal.widget.LockPatternUtils.pinOrPasswordQualityT import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential; import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_STRONG; import static com.android.server.locksettings.SyntheticPasswordManager.TOKEN_TYPE_WEAK; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.decryptProfilePassword; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.encryptProfilePassword; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.profilePasswordDecryptAlias; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.profilePasswordEncryptAlias; import static com.android.server.locksettings.UnifiedProfilePasswordCrypto.removeKeystoreProfileKey; import android.Manifest; import android.annotation.NonNull; Loading Loading @@ -103,12 +108,10 @@ import android.provider.Settings; import android.security.AndroidKeyStoreMaintenance; import android.security.KeyStoreAuthorization; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import android.security.keystore.recovery.KeyChainProtectionParams; import android.security.keystore.recovery.KeyChainSnapshot; import android.security.keystore.recovery.RecoveryCertPath; import android.security.keystore.recovery.WrappedApplicationKey; import android.security.keystore2.AndroidKeyStoreLoadStoreParameter; import android.security.keystore2.AndroidKeyStoreProvider; import android.service.gatekeeper.IGateKeeperService; import android.service.notification.StatusBarNotification; Loading Loading @@ -163,13 +166,11 @@ import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.text.SimpleDateFormat; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.Enumeration; import java.util.List; Loading @@ -183,12 +184,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; /** * LockSettingsService (LSS) mainly has the following responsibilities: Loading Loading @@ -231,7 +228,6 @@ import javax.crypto.spec.GCMParameterSpec; public class LockSettingsService extends ILockSettings.Stub { private static final String TAG = "LockSettingsService"; private static final int PROFILE_KEY_IV_SIZE = 12; private static final String SEPARATE_PROFILE_CHALLENGE_KEY = "lockscreen.profilechallenge"; private static final String PREV_LSKF_BASED_PROTECTOR_ID_KEY = "prev-sp-handle"; private static final String LSKF_LAST_CHANGED_TIME_KEY = "sp-handle-ts"; Loading @@ -252,9 +248,6 @@ public class LockSettingsService extends ILockSettings.Stub { // user's credential must be presented again, e.g. via ConfirmLockPattern/ConfirmLockPassword. private static final int GK_PW_HANDLE_STORE_DURATION_MS = 10 * 60 * 1000; // 10 minutes private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_"; private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_"; private static final int HEADLESS_VENDOR_AUTH_SECRET_LENGTH = 32; // Order of holding lock: Loading Loading @@ -624,11 +617,7 @@ public class LockSettingsService extends ILockSettings.Stub { public KeyStore getKeyStore() { try { KeyStore ks = KeyStore.getInstance( SyntheticPasswordCrypto.androidKeystoreProviderName()); ks.load(new AndroidKeyStoreLoadStoreParameter( SyntheticPasswordCrypto.keyNamespace())); return ks; return SyntheticPasswordCrypto.getKeyStore(); } catch (Exception e) { throw new IllegalStateException("Cannot load keystore", e); } Loading Loading @@ -1359,7 +1348,7 @@ public class LockSettingsService extends ILockSettings.Stub { try { if (enabled) { mStorage.removeChildProfileLock(userId); removeKeystoreProfileKey(userId); removeKeystoreProfileKey(mKeyStore, userId); } else { synchronized (mSpManager) { tieProfileLockIfNecessary(userId, profileUserPassword); Loading Loading @@ -1551,21 +1540,7 @@ public class LockSettingsService extends ILockSettings.Stub { if (storedData == null) { throw new FileNotFoundException("Child profile lock file not found"); } byte[] iv = Arrays.copyOfRange(storedData, 0, PROFILE_KEY_IV_SIZE); byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE, storedData.length); byte[] decryptionResult; SecretKey decryptionKey = (SecretKey) mKeyStore.getKey( profilePasswordDecryptAlias(userId), null); Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv)); decryptionResult = cipher.doFinal(encryptedPassword); LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword( decryptionResult); ArrayUtils.zeroize(decryptionResult); LockscreenCredential credential = decryptProfilePassword(mKeyStore, userId, storedData); try { long parentSid = getGateKeeperService().getSecureUserId( mUserManager.getProfileParent(userId).id); Loading Loading @@ -1749,7 +1724,7 @@ public class LockSettingsService extends ILockSettings.Stub { profileUserId, /* isLockTiedToParent= */ true); mStorage.removeChildProfileLock(profileUserId); removeKeystoreProfileKey(profileUserId); removeKeystoreProfileKey(mKeyStore, profileUserId); } else { Slog.wtf(TAG, "Attempt to clear tied challenge, but no password supplied."); } Loading Loading @@ -2168,60 +2143,15 @@ public class LockSettingsService extends ILockSettings.Stub { LockscreenCredential password) { Slogf.i(TAG, "Tying lock for profile user %d to parent user %d", profileUserId, parentUserId); final byte[] iv; final byte[] ciphertext; final long parentSid; try { parentSid = getGateKeeperService().getSecureUserId(parentUserId); } catch (RemoteException e) { throw new IllegalStateException("Failed to talk to GateKeeper service", e); } try { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); keyGenerator.init(new SecureRandom()); SecretKey secretKey = keyGenerator.generateKey(); try { mKeyStore.setEntry( profilePasswordEncryptAlias(profileUserId), new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); mKeyStore.setEntry( profilePasswordDecryptAlias(profileUserId), new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) .setBoundToSpecificSecureUserId(parentSid) .setUserAuthenticationValidityDurationSeconds(30) .build()); // Key imported, obtain a reference to it. SecretKey keyStoreEncryptionKey = (SecretKey) mKeyStore.getKey( profilePasswordEncryptAlias(profileUserId), null); Cipher cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey); ciphertext = cipher.doFinal(password.getCredential()); iv = cipher.getIV(); } finally { // The original key can now be discarded. mKeyStore.deleteEntry(profilePasswordEncryptAlias(profileUserId)); } } catch (UnrecoverableKeyException | BadPaddingException | IllegalBlockSizeException | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { throw new IllegalStateException("Failed to encrypt key", e); } if (iv.length != PROFILE_KEY_IV_SIZE) { throw new IllegalArgumentException("Invalid iv length: " + iv.length); } mStorage.writeChildProfileLock(profileUserId, ArrayUtils.concat(iv, ciphertext)); byte[] encryptedPasswordData = encryptProfilePassword(mKeyStore, profileUserId, parentSid, password); mStorage.writeChildProfileLock(profileUserId, encryptedPasswordData); } private void setCeStorageProtection(@UserIdInt int userId, SyntheticPassword sp) { Loading Loading @@ -2708,37 +2638,12 @@ public class LockSettingsService extends ILockSettings.Stub { mUnifiedProfilePasswordCache.removePassword(userId); gateKeeperClearSecureUserId(userId); removeKeystoreProfileKey(userId); removeKeystoreProfileKey(mKeyStore, userId); // Clean up storage last, so that removeStateForReusedUserIdIfNecessary() can assume that no // USER_SERIAL_NUMBER_KEY means user is fully removed. mStorage.removeUser(userId); } // TODO: b/412331826 Add protectorId param private static String profilePasswordEncryptAlias(int profileUserId) { return PROFILE_KEY_NAME_ENCRYPT + profileUserId; } // TODO: b/412331826 Add protectorId param private static String profilePasswordDecryptAlias(int profileUserId) { return PROFILE_KEY_NAME_DECRYPT + profileUserId; } private void removeKeystoreProfileKey(int targetUserId) { final String encryptAlias = profilePasswordEncryptAlias(targetUserId); final String decryptAlias = profilePasswordDecryptAlias(targetUserId); try { if (mKeyStore.containsAlias(encryptAlias) || mKeyStore.containsAlias(decryptAlias)) { Slogf.i(TAG, "Removing keystore profile key for user %d", targetUserId); mKeyStore.deleteEntry(encryptAlias); mKeyStore.deleteEntry(decryptAlias); } } catch (KeyStoreException e) { // We have tried our best to remove the key. Slogf.e(TAG, e, "Error removing keystore profile key for user %d", targetUserId); } } @Override public void registerStrongAuthTracker(IStrongAuthTracker tracker) { checkPasswordReadPermission(); Loading
services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java +1 −1 Original line number Diff line number Diff line Loading @@ -155,7 +155,7 @@ class SyntheticPasswordCrypto { return KeyProperties.NAMESPACE_LOCKSETTINGS; } private static KeyStore getKeyStore() static KeyStore getKeyStore() throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException { KeyStore keyStore = KeyStore.getInstance(androidKeystoreProviderName()); keyStore.load(new AndroidKeyStoreLoadStoreParameter(keyNamespace())); Loading
services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCrypto.java 0 → 100644 +169 −0 Original line number Diff line number Diff line /* * Copyright 2025 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.server.locksettings; import android.security.keystore.KeyProperties; import android.security.keystore.KeyProtection; import com.android.internal.util.ArrayUtils; import com.android.internal.widget.LockscreenCredential; import com.android.server.utils.Slogf; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; import java.util.Arrays; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyGenerator; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.GCMParameterSpec; /** * Helpers for the cryptography related to managing unified passwords for child profiles. */ class UnifiedProfilePasswordCrypto { private static final String TAG = "UnifiedProfilePasswordCrypto"; private static final String PROFILE_KEY_NAME_ENCRYPT = "profile_key_name_encrypt_"; private static final String PROFILE_KEY_NAME_DECRYPT = "profile_key_name_decrypt_"; private static final int PROFILE_KEY_IV_SIZE = 12; private UnifiedProfilePasswordCrypto() { } /** * Decrypts the given byte array using the parent-bound key into a {@link LockscreenCredential}. * The input format is expected to match the output of the * {@link #encryptProfilePassword(KeyStore, int, long, LockscreenCredential)} method. */ static LockscreenCredential decryptProfilePassword(KeyStore keyStore, int userId, byte[] storedData) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnrecoverableKeyException, KeyStoreException { byte[] iv = Arrays.copyOfRange(storedData, 0, PROFILE_KEY_IV_SIZE); byte[] encryptedPassword = Arrays.copyOfRange(storedData, PROFILE_KEY_IV_SIZE, storedData.length); byte[] decryptionResult; SecretKey decryptionKey = (SecretKey) keyStore.getKey( profilePasswordDecryptAlias(userId), null); Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv)); decryptionResult = cipher.doFinal(encryptedPassword); LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword( decryptionResult); ArrayUtils.zeroize(decryptionResult); return credential; } /** * Creates a parent-bound key and encrypts the given password with it. The result is a byte * array as follows: * * <pre> * <- PROFILE_KEY_IV_SIZE -> * [ iv , ciphertext ] * </pre> */ static byte[] encryptProfilePassword(KeyStore keyStore, int profileUserId, long parentSid, LockscreenCredential password) { final byte[] iv; final byte[] ciphertext; try { KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES); keyGenerator.init(new SecureRandom()); SecretKey secretKey = keyGenerator.generateKey(); try { keyStore.setEntry( profilePasswordEncryptAlias(profileUserId), new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build()); keyStore.setEntry( profilePasswordDecryptAlias(profileUserId), new KeyStore.SecretKeyEntry(secretKey), new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .setUserAuthenticationRequired(true) .setBoundToSpecificSecureUserId(parentSid) .setUserAuthenticationValidityDurationSeconds(30) .build()); // Key imported, obtain a reference to it. SecretKey keyStoreEncryptionKey = (SecretKey) keyStore.getKey( profilePasswordEncryptAlias(profileUserId), null); Cipher cipher = Cipher.getInstance( KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_GCM + "/" + KeyProperties.ENCRYPTION_PADDING_NONE); cipher.init(Cipher.ENCRYPT_MODE, keyStoreEncryptionKey); ciphertext = cipher.doFinal(password.getCredential()); iv = cipher.getIV(); } finally { // The original key can now be discarded. keyStore.deleteEntry(profilePasswordEncryptAlias(profileUserId)); } } catch (UnrecoverableKeyException | BadPaddingException | IllegalBlockSizeException | KeyStoreException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) { throw new IllegalStateException("Failed to encrypt key", e); } if (iv.length != PROFILE_KEY_IV_SIZE) { throw new IllegalArgumentException("Invalid iv length: " + iv.length); } return ArrayUtils.concat(iv, ciphertext); } // TODO: b/412331826 Add protectorId param static String profilePasswordEncryptAlias(int profileUserId) { return PROFILE_KEY_NAME_ENCRYPT + profileUserId; } // TODO: b/412331826 Add protectorId param static String profilePasswordDecryptAlias(int profileUserId) { return PROFILE_KEY_NAME_DECRYPT + profileUserId; } /** Cleans up the keystore entries for the profile password's encrypt/decrypt keys. */ static void removeKeystoreProfileKey(KeyStore keyStore, int targetUserId) { final String encryptAlias = profilePasswordEncryptAlias(targetUserId); final String decryptAlias = profilePasswordDecryptAlias(targetUserId); try { if (keyStore.containsAlias(encryptAlias) || keyStore.containsAlias(decryptAlias)) { Slogf.i(TAG, "Removing keystore profile key for user %d", targetUserId); keyStore.deleteEntry(encryptAlias); keyStore.deleteEntry(decryptAlias); } } catch (KeyStoreException e) { // We have tried our best to remove the key. Slogf.e(TAG, e, "Error removing keystore profile key for user %d", targetUserId); } } }