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

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

Merge "Implement fake keystore for LockSettingsService unit tests." into main

parents 0a24b529 2e342a3a
Loading
Loading
Loading
Loading
+17 −9
Original line number Diff line number Diff line
@@ -1574,12 +1574,17 @@ public class LockSettingsService extends ILockSettings.Stub {
        mKeyStoreAuthorization.onUserStorageLocked(userId);
    }

    @VisibleForTesting /** Note: this method is overridden in unit tests */
    protected LockscreenCredential getDecryptedPasswordForUnifiedProfile(int userId)
            throws KeyStoreException, UnrecoverableKeyException,
            NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
            InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException,
            CertificateException, IOException {
    private LockscreenCredential getDecryptedPasswordForUnifiedProfile(int userId)
            throws KeyStoreException,
                    UnrecoverableKeyException,
                    NoSuchAlgorithmException,
                    NoSuchPaddingException,
                    InvalidKeyException,
                    InvalidAlgorithmParameterException,
                    IllegalBlockSizeException,
                    BadPaddingException,
                    CertificateException,
                    IOException {
        Slogf.d(TAG, "Decrypting password for unified profile %d", userId);
        byte[] storedData = mStorage.readChildProfileLock(userId);
        if (storedData == null) {
@@ -2193,9 +2198,12 @@ public class LockSettingsService extends ILockSettings.Stub {
        }
    }

    @VisibleForTesting /** Note: this method is overridden in unit tests */
    protected void tieProfileLockToParent(int profileUserId, int parentUserId,
            LockscreenCredential password) {
    /**
     * Sets up an encrypted password protected by a new encryption key bound to the parent sid. The
     * credential must be a PASSWORD, rather than NONE.
     */
    private void tieProfileLockToParent(
            int profileUserId, int parentUserId, LockscreenCredential password) {
        Slogf.i(TAG, "Tying lock for profile user %d to parent user %d", profileUserId,
                parentUserId);
        final long parentSid;
+5 −1
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.server.LocalServices;
import com.android.server.StorageManagerInternal;
import com.android.server.locksettings.FakeKeyStore.FakeKeyStoreRule;
import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreManager;
import com.android.server.pm.UserManagerInternal;
import com.android.server.security.authenticationpolicy.SecureLockDeviceServiceInternal;
@@ -123,6 +124,8 @@ public abstract class BaseLockSettingsServiceTests {
    @Rule
    public PropertyInvalidatedCacheTestRule mCacheRule = new PropertyInvalidatedCacheTestRule();

    @Rule public FakeKeyStoreRule mKeyStoreRule = new FakeKeyStoreRule();

    @Before
    public void setUp_baseServices() throws Exception {
        mResources = createMockResources();
@@ -187,7 +190,8 @@ public abstract class BaseLockSettingsServiceTests {
                        mRecoverableKeyStoreManager,
                        mUserManagerInternal,
                        mDeviceStateCache,
                        mSecureLockDeviceServiceInternal);
                        mSecureLockDeviceServiceInternal,
                        mKeyStoreRule.getKeyStore());
        mService =
                new LockSettingsServiceTestable(mInjector, mGateKeeperService, mAuthSecretService);
        mService.mHasSecureLockScreen = true;
+207 −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.keystore2.AndroidKeyStoreLoadStoreParameter;
import android.util.ArrayMap;

import com.android.internal.util.Preconditions;

import org.junit.rules.ExternalResource;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Date;
import java.util.Enumeration;

import javax.crypto.SecretKey;

/**
 * An implementation of an in-memory keystore for testing LockSettingsService. Implement methods as
 * needed for new functionality.
 */
public final class FakeKeyStore {
    public static final String NAME = "FakeKeyStore";

    private FakeKeyStore() {}

    public static class FakeKeyStoreSpi extends KeyStoreSpi {

        private final ArrayMap<String, SecretKey> mKeyByAlias = new ArrayMap<>();

        @Override
        public Key engineGetKey(String alias, char[] password)
                throws NoSuchAlgorithmException, UnrecoverableKeyException {
            return mKeyByAlias.get(alias);
        }

        @Override
        public Certificate[] engineGetCertificateChain(String alias) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Certificate engineGetCertificate(String alias) {
            throw new UnsupportedOperationException();
        }

        @Override
        public Date engineGetCreationDate(String alias) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void engineSetEntry(
                String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam)
                throws KeyStoreException {
            mKeyByAlias.put(alias, ((KeyStore.SecretKeyEntry) entry).getSecretKey());
        }

        @Override
        public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
                throws KeyStoreException {
            throw new UnsupportedOperationException();
        }

        @Override
        public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain)
                throws KeyStoreException {
            throw new UnsupportedOperationException();
        }

        @Override
        public void engineSetCertificateEntry(String alias, Certificate cert)
                throws KeyStoreException {
            throw new UnsupportedOperationException();
        }

        @Override
        public void engineDeleteEntry(String alias) throws KeyStoreException {
            mKeyByAlias.remove(alias);
        }

        @Override
        public Enumeration<String> engineAliases() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean engineContainsAlias(String alias) {
            return mKeyByAlias.containsKey(alias);
        }

        @Override
        public int engineSize() {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean engineIsKeyEntry(String alias) {
            throw new UnsupportedOperationException();
        }

        @Override
        public boolean engineIsCertificateEntry(String alias) {
            throw new UnsupportedOperationException();
        }

        @Override
        public String engineGetCertificateAlias(Certificate cert) {
            throw new UnsupportedOperationException();
        }

        @Override
        public void engineStore(OutputStream stream, char[] password)
                throws IOException, NoSuchAlgorithmException, CertificateException {
            throw new UnsupportedOperationException();
        }

        @Override
        public void engineLoad(KeyStore.LoadStoreParameter param)
                throws IOException, NoSuchAlgorithmException, CertificateException {
            Preconditions.checkArgument(param instanceof AndroidKeyStoreLoadStoreParameter);
        }

        @Override
        public void engineLoad(InputStream stream, char[] password)
                throws IOException, NoSuchAlgorithmException, CertificateException {}
    }

    private static class FakeKeyStoreProvider extends Provider {
        FakeKeyStoreProvider() {
            super(NAME, 1.0, "Fake KeyStore Provider");

            put("KeyStore." + NAME, FakeKeyStoreSpi.class.getName());
        }
    }

    /**
     * Install a fake in-memory keystore for testing LockSettingsService.
     *
     * @return an instance of the fake keystore.
     */
    private static KeyStore installFakeKeyStore() {
        Security.addProvider(new FakeKeyStoreProvider());
        final KeyStore keyStore;
        try {
            keyStore = KeyStore.getInstance(FakeKeyStore.NAME);
            keyStore.load(
                    new AndroidKeyStoreLoadStoreParameter(KeyProperties.NAMESPACE_APPLICATION));
            return keyStore;
        } catch (KeyStoreException
                | IOException
                | NoSuchAlgorithmException
                | CertificateException e) {
            throw new IllegalStateException(e);
        }
    }

    private static void uninstallFakeKeyStore() {
        Security.removeProvider(FakeKeyStore.NAME);
    }

    /** Installs an in-memory keystore for the duration of a JUnit test. */
    public static class FakeKeyStoreRule extends ExternalResource {
        private KeyStore mKeyStore;

        @Override
        protected void before() throws Throwable {
            mKeyStore = installFakeKeyStore();
        }

        @Override
        protected void after() {
            uninstallFakeKeyStore();
        }

        public KeyStore getKeyStore() {
            return mKeyStore;
        }
    }
}
+9 −39
Original line number Diff line number Diff line
@@ -25,15 +25,11 @@ import android.content.Intent;
import android.content.pm.UserInfo;
import android.hardware.authsecret.IAuthSecret;
import android.os.Handler;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.storage.IStorageManager;
import android.security.keystore.KeyPermanentlyInvalidatedException;
import android.service.gatekeeper.IGateKeeperService;

import com.android.internal.widget.LockscreenCredential;
import com.android.server.ServiceThread;
import com.android.server.StorageManagerInternal;
import com.android.server.locksettings.SyntheticPasswordManager.SyntheticPassword;
@@ -41,7 +37,6 @@ import com.android.server.locksettings.recoverablekeystore.RecoverableKeyStoreMa
import com.android.server.pm.UserManagerInternal;
import com.android.server.security.authenticationpolicy.SecureLockDeviceServiceInternal;

import java.io.FileNotFoundException;
import java.security.KeyStore;
import java.time.Duration;

@@ -65,6 +60,7 @@ public class LockSettingsServiceTestable extends LockSettingsService {
        private UserManagerInternal mUserManagerInternal;
        private DeviceStateCache mDeviceStateCache;
        private Duration mTimeSinceBoot;
        private KeyStore mKeyStore;

        public boolean mIsHeadlessSystemUserMode = false;

@@ -81,7 +77,8 @@ public class LockSettingsServiceTestable extends LockSettingsService {
                RecoverableKeyStoreManager recoverableKeyStoreManager,
                UserManagerInternal userManagerInternal,
                DeviceStateCache deviceStateCache,
                SecureLockDeviceServiceInternal secureLockDeviceServiceInternal) {
                SecureLockDeviceServiceInternal secureLockDeviceServiceInternal,
                KeyStore keyStore) {
            super(context);
            mLockSettingsStorage = storage;
            mStrongAuth = strongAuth;
@@ -95,6 +92,7 @@ public class LockSettingsServiceTestable extends LockSettingsService {
            mUserManagerInternal = userManagerInternal;
            mDeviceStateCache = deviceStateCache;
            mSecureLockDeviceServiceInternal = secureLockDeviceServiceInternal;
            mKeyStore = keyStore;
        }

        @Override
@@ -177,6 +175,11 @@ public class LockSettingsServiceTestable extends LockSettingsService {
            return mIsHeadlessSystemUserMode;
        }

        @Override
        public KeyStore getKeyStore() {
            return mKeyStore;
        }

        void setTimeSinceBoot(Duration time) {
            mTimeSinceBoot = time;
        }
@@ -199,39 +202,6 @@ public class LockSettingsServiceTestable extends LockSettingsService {
        mAuthSecretService = authSecretService;
    }

    @Override
    protected void tieProfileLockToParent(int profileUserId, int parentUserId,
            LockscreenCredential password) {
        Parcel parcel = Parcel.obtain();
        parcel.writeParcelable(password, 0);
        mStorage.writeChildProfileLock(profileUserId, parcel.marshall());
        parcel.recycle();
    }

    @Override
    protected LockscreenCredential getDecryptedPasswordForUnifiedProfile(int userId)
            throws FileNotFoundException, KeyPermanentlyInvalidatedException {
        byte[] storedData = mStorage.readChildProfileLock(userId);
        if (storedData == null) {
            throw new FileNotFoundException("Child profile lock file not found");
        }
        try {
            if (mGateKeeperService.getSecureUserId(userId) == 0) {
                throw new KeyPermanentlyInvalidatedException();
            }
        } catch (RemoteException e) {
            // shouldn't happen.
        }
        Parcel parcel = Parcel.obtain();
        try {
            parcel.unmarshall(storedData, 0, storedData.length);
            parcel.setDataPosition(0);
            return (LockscreenCredential) parcel.readParcelable(null);
        } finally {
            parcel.recycle();
        }
    }

    @Override
    void initKeystoreSuperKeys(int userId, SyntheticPassword sp, boolean allowExisting) {
    }
+1 −0
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ public class LockscreenRepairModeTest extends BaseLockSettingsServiceTests {
    @Before
    public void setUp() throws Exception {
        mService.initializeSyntheticPassword(PRIMARY_USER_ID);
        mService.initializeSyntheticPassword(MANAGED_PROFILE_USER_ID);
    }

    @Test
Loading