Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2e961148 authored by Billy Huang's avatar Billy Huang Committed by Android (Google) Code Review
Browse files

Merge changes I03e196bd,Ie0afecac into main

* changes:
  Inject KeyStore from SyntheticPasswordCrypto
  Extract unified profile password crypto helper
parents 98e2bfb0 254035c9
Loading
Loading
Loading
Loading
+13 −108
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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:
@@ -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";
@@ -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:
@@ -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);
            }
@@ -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);
@@ -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);
@@ -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.");
                    }
@@ -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) {
@@ -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();
+1 −1
Original line number Diff line number Diff line
@@ -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()));
+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);
        }
    }
}