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

Commit b7982ccc authored by Tianjie Xu's avatar Tianjie Xu Committed by Automerger Merge Worker
Browse files

Merge changes I45054e68,I6f301314 am: 2befe56d am: 17cf7a64 am: 15c3e45a am: 2e4306e9

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1555542

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ia85b9c0739c095a7929ddb4a49f293782a76f17f
parents 40025576 2e4306e9
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ class LockSettingsStorage {
    private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";

    private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key";
    private static final String REBOOT_ESCROW_SERVER_BLOB = "reboot.escrow.server.blob.key";

    private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";

@@ -318,6 +319,22 @@ class LockSettingsStorage {
        deleteFile(getRebootEscrowFile(userId));
    }

    public void writeRebootEscrowServerBlob(byte[] serverBlob) {
        writeFile(getRebootEscrowServerBlob(), serverBlob);
    }

    public byte[] readRebootEscrowServerBlob() {
        return readFile(getRebootEscrowServerBlob());
    }

    public boolean hasRebootEscrowServerBlob() {
        return hasFile(getRebootEscrowServerBlob());
    }

    public void removeRebootEscrowServerBlob() {
        deleteFile(getRebootEscrowServerBlob());
    }

    public boolean hasPassword(int userId) {
        return hasFile(getLockPasswordFilename(userId));
    }
@@ -446,6 +463,12 @@ class LockSettingsStorage {
        return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE);
    }

    @VisibleForTesting
    String getRebootEscrowServerBlob() {
        // There is a single copy of server blob for all users.
        return getLockCredentialFilePathForUser(UserHandle.USER_SYSTEM, REBOOT_ESCROW_SERVER_BLOB);
    }

    private String getLockCredentialFilePathForUser(int userId, String basename) {
        String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
                        SYSTEM_DIRECTORY;
+18 −10
Original line number Diff line number Diff line
@@ -124,26 +124,28 @@ class RebootEscrowManager {
    static class Injector {
        protected Context mContext;
        private final RebootEscrowKeyStoreManager mKeyStoreManager;
        private final RebootEscrowProviderInterface mRebootEscrowProvider;
        private final LockSettingsStorage mStorage;
        private RebootEscrowProviderInterface mRebootEscrowProvider;

        Injector(Context context) {
        Injector(Context context, LockSettingsStorage storage) {
            mContext = context;
            mStorage = storage;
            mKeyStoreManager = new RebootEscrowKeyStoreManager();
        }

            RebootEscrowProviderInterface rebootEscrowProvider = null;
            // TODO(xunchang) add implementation for server based ror.
        private RebootEscrowProviderInterface createRebootEscrowProvider() {
            RebootEscrowProviderInterface rebootEscrowProvider;
            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
                    "server_based_ror_enabled", false)) {
                Slog.e(TAG, "Server based ror isn't implemented yet.");
                rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage);
            } else {
                rebootEscrowProvider = new RebootEscrowProviderHalImpl();
            }

            if (rebootEscrowProvider != null && rebootEscrowProvider.hasRebootEscrowSupport()) {
                mRebootEscrowProvider = rebootEscrowProvider;
            } else {
                mRebootEscrowProvider = null;
            if (rebootEscrowProvider.hasRebootEscrowSupport()) {
                return rebootEscrowProvider;
            }
            return null;
        }

        public Context getContext() {
@@ -159,6 +161,12 @@ class RebootEscrowManager {
        }

        public RebootEscrowProviderInterface getRebootEscrowProvider() {
            // Initialize for the provider lazily. Because the device_config and service
            // implementation apps may change when system server is running.
            if (mRebootEscrowProvider == null) {
                mRebootEscrowProvider = createRebootEscrowProvider();
            }

            return mRebootEscrowProvider;
        }

@@ -177,7 +185,7 @@ class RebootEscrowManager {
    }

    RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
        this(new Injector(context), callbacks, storage);
        this(new Injector(context, storage), callbacks, storage);
    }

    @VisibleForTesting
+202 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 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.annotation.Nullable;
import android.content.Context;
import android.os.RemoteException;
import android.provider.DeviceConfig;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

import javax.crypto.SecretKey;

/**
 * An implementation of the {@link RebootEscrowProviderInterface} by communicating with server to
 * encrypt & decrypt the blob.
 */
class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterface {
    private static final String TAG = "RebootEscrowProvider";

    // Timeout for service binding
    private static final long DEFAULT_SERVICE_TIMEOUT_IN_SECONDS = 10;

    /**
     * Use the default lifetime of 10 minutes. The lifetime covers the following activities:
     * Server wrap secret -> device reboot -> server unwrap blob.
     */
    private static final long DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS = 600_1000;

    private final LockSettingsStorage mStorage;

    private final Injector mInjector;

    static class Injector {
        private ResumeOnRebootServiceConnection mServiceConnection = null;

        Injector(Context context) {
            mServiceConnection = new ResumeOnRebootServiceProvider(context).getServiceConnection();
            if (mServiceConnection == null) {
                Slog.e(TAG, "Failed to resolve resume on reboot server service.");
            }
        }

        Injector(ResumeOnRebootServiceConnection serviceConnection) {
            mServiceConnection = serviceConnection;
        }

        @Nullable
        private ResumeOnRebootServiceConnection getServiceConnection() {
            return mServiceConnection;
        }

        long getServiceTimeoutInSeconds() {
            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA,
                    "server_based_service_timeout_in_seconds",
                    DEFAULT_SERVICE_TIMEOUT_IN_SECONDS);
        }

        long getServerBlobLifetimeInMillis() {
            return DeviceConfig.getLong(DeviceConfig.NAMESPACE_OTA,
                    "server_based_server_blob_lifetime_in_millis",
                    DEFAULT_SERVER_BLOB_LIFETIME_IN_MILLIS);
        }
    }

    RebootEscrowProviderServerBasedImpl(Context context, LockSettingsStorage storage) {
        this(storage, new Injector(context));
    }

    @VisibleForTesting
    RebootEscrowProviderServerBasedImpl(LockSettingsStorage storage, Injector injector) {
        mStorage = storage;
        mInjector = injector;
    }

    @Override
    public boolean hasRebootEscrowSupport() {
        return mInjector.getServiceConnection() != null;
    }

    private byte[] unwrapServerBlob(byte[] serverBlob, SecretKey decryptionKey) throws
            TimeoutException, RemoteException, IOException {
        ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection();
        if (serviceConnection == null) {
            Slog.w(TAG, "Had reboot escrow data for users, but resume on reboot server"
                    + " service is unavailable");
            return null;
        }

        // Decrypt with k_k from the key store first.
        byte[] decryptedBlob = AesEncryptionUtil.decrypt(decryptionKey, serverBlob);
        if (decryptedBlob == null) {
            Slog.w(TAG, "Decrypted server blob should not be null");
            return null;
        }

        // Ask the server connection service to decrypt the inner layer, to get the reboot
        // escrow key (k_s).
        serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds());
        byte[] escrowKeyBytes = serviceConnection.unwrap(decryptedBlob,
                mInjector.getServiceTimeoutInSeconds());
        serviceConnection.unbindService();

        return escrowKeyBytes;
    }

    @Override
    public RebootEscrowKey getAndClearRebootEscrowKey(SecretKey decryptionKey) {
        byte[] serverBlob = mStorage.readRebootEscrowServerBlob();
        // Delete the server blob in storage.
        mStorage.removeRebootEscrowServerBlob();
        if (serverBlob == null) {
            Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
            return null;
        }

        try {
            byte[] escrowKeyBytes = unwrapServerBlob(serverBlob, decryptionKey);
            if (escrowKeyBytes == null) {
                Slog.w(TAG, "Decrypted reboot escrow key bytes should not be null");
                return null;
            } else if (escrowKeyBytes.length != 32) {
                Slog.e(TAG, "Decrypted reboot escrow key has incorrect size "
                        + escrowKeyBytes.length);
                return null;
            }

            return RebootEscrowKey.fromKeyBytes(escrowKeyBytes);
        } catch (TimeoutException | RemoteException | IOException e) {
            Slog.w(TAG, "Failed to decrypt the server blob ", e);
            return null;
        }
    }

    @Override
    public void clearRebootEscrowKey() {
        mStorage.removeRebootEscrowServerBlob();
    }

    private byte[] wrapEscrowKey(byte[] escrowKeyBytes, SecretKey encryptionKey) throws
            TimeoutException, RemoteException, IOException {
        ResumeOnRebootServiceConnection serviceConnection = mInjector.getServiceConnection();
        if (serviceConnection == null) {
            Slog.w(TAG, "Failed to encrypt the reboot escrow key: resume on reboot server"
                    + " service is unavailable");
            return null;
        }

        serviceConnection.bindToService(mInjector.getServiceTimeoutInSeconds());
        // Ask the server connection service to encrypt the reboot escrow key.
        byte[] serverEncryptedBlob = serviceConnection.wrapBlob(escrowKeyBytes,
                mInjector.getServerBlobLifetimeInMillis(), mInjector.getServiceTimeoutInSeconds());
        serviceConnection.unbindService();

        if (serverEncryptedBlob == null) {
            Slog.w(TAG, "Server encrypted reboot escrow key cannot be null");
            return null;
        }

        // Additionally wrap the server blob with a local key.
        return AesEncryptionUtil.encrypt(encryptionKey, serverEncryptedBlob);
    }

    @Override
    public boolean storeRebootEscrowKey(RebootEscrowKey escrowKey, SecretKey encryptionKey) {
        mStorage.removeRebootEscrowServerBlob();
        try {
            byte[] wrappedBlob = wrapEscrowKey(escrowKey.getKeyBytes(), encryptionKey);
            if (wrappedBlob == null) {
                Slog.w(TAG, "Failed to encrypt the reboot escrow key");
                return false;
            }
            mStorage.writeRebootEscrowServerBlob(wrappedBlob);

            Slog.i(TAG, "Reboot escrow key encrypted and stored.");
            return true;
        } catch (TimeoutException | RemoteException | IOException e) {
            Slog.w(TAG, "Failed to encrypt the reboot escrow key ", e);
        }

        return false;
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -81,6 +81,11 @@ public class LockSettingsStorageTestable extends LockSettingsStorage {
                super.getChildProfileLockFile(userId)).getAbsolutePath();
    }

    @Override
    String getRebootEscrowServerBlob() {
        return makeDirs(mStorageDir, super.getRebootEscrowServerBlob()).getAbsolutePath();
    }

    @Override
    protected File getSyntheticPasswordDirectoryForUser(int userId) {
        return makeDirs(mStorageDir, super.getSyntheticPasswordDirectoryForUser(
+99 −3
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
@@ -52,6 +53,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.internal.widget.RebootEscrowListener;
import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection;

import org.junit.Before;
import org.junit.Test;
@@ -92,6 +94,7 @@ public class RebootEscrowManagerTests {
    private UserManager mUserManager;
    private RebootEscrowManager.Callbacks mCallbacks;
    private IRebootEscrow mRebootEscrow;
    private ResumeOnRebootServiceConnection mServiceConnection;
    private RebootEscrowKeyStoreManager mKeyStoreManager;

    LockSettingsStorageTestable mStorage;
@@ -108,6 +111,7 @@ public class RebootEscrowManagerTests {

    static class MockInjector extends RebootEscrowManager.Injector {
        private final IRebootEscrow mRebootEscrow;
        private final ResumeOnRebootServiceConnection mServiceConnection;
        private final RebootEscrowProviderInterface mRebootEscrowProvider;
        private final UserManager mUserManager;
        private final MockableRebootEscrowInjected mInjected;
@@ -116,10 +120,11 @@ public class RebootEscrowManagerTests {
        MockInjector(Context context, UserManager userManager,
                IRebootEscrow rebootEscrow,
                RebootEscrowKeyStoreManager keyStoreManager,
                LockSettingsStorageTestable storage,
                MockableRebootEscrowInjected injected) {
            super(context);
            super(context, storage);
            mRebootEscrow = rebootEscrow;

            mServiceConnection = null;
            RebootEscrowProviderHalImpl.Injector halInjector =
                    new RebootEscrowProviderHalImpl.Injector() {
                        @Override
@@ -133,6 +138,22 @@ public class RebootEscrowManagerTests {
            mInjected = injected;
        }

        MockInjector(Context context, UserManager userManager,
                ResumeOnRebootServiceConnection serviceConnection,
                RebootEscrowKeyStoreManager keyStoreManager,
                LockSettingsStorageTestable storage,
                MockableRebootEscrowInjected injected) {
            super(context, storage);
            mServiceConnection = serviceConnection;
            mRebootEscrow = null;
            RebootEscrowProviderServerBasedImpl.Injector injector =
                    new RebootEscrowProviderServerBasedImpl.Injector(serviceConnection);
            mRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(storage, injector);
            mUserManager = userManager;
            mKeyStoreManager = keyStoreManager;
            mInjected = injected;
        }

        @Override
        public UserManager getUserManager() {
            return mUserManager;
@@ -165,6 +186,7 @@ public class RebootEscrowManagerTests {
        mUserManager = mock(UserManager.class);
        mCallbacks = mock(RebootEscrowManager.Callbacks.class);
        mRebootEscrow = mock(IRebootEscrow.class);
        mServiceConnection = mock(ResumeOnRebootServiceConnection.class);
        mKeyStoreManager = mock(RebootEscrowKeyStoreManager.class);
        mAesKey = new SecretKeySpec(TEST_AES_KEY, "AES");

@@ -186,7 +208,12 @@ public class RebootEscrowManagerTests {
        when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
        mInjected = mock(MockableRebootEscrowInjected.class);
        mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow,
                mKeyStoreManager, mInjected), mCallbacks, mStorage);
                mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage);
    }

    private void setServerBasedRebootEscrowProvider() throws Exception {
        mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager,
                mServiceConnection, mKeyStoreManager, mStorage, mInjected), mCallbacks, mStorage);
    }

    @Test
@@ -201,6 +228,19 @@ public class RebootEscrowManagerTests {
        verify(mRebootEscrow, never()).storeKey(any());
    }

    @Test
    public void prepareRebootEscrowServerBased_Success() throws Exception {
        setServerBasedRebootEscrowProvider();
        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
        mService.setRebootEscrowListener(mockListener);
        mService.prepareRebootEscrow();

        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
        verify(mockListener).onPreparedForReboot(eq(true));
        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
        assertFalse(mStorage.hasRebootEscrowServerBlob());
    }

    @Test
    public void prepareRebootEscrow_ClearCredentials_Success() throws Exception {
        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -245,6 +285,28 @@ public class RebootEscrowManagerTests {
        assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
    }

    @Test
    public void armServiceServerBased_Success() throws Exception {
        setServerBasedRebootEscrowProvider();
        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
        mService.setRebootEscrowListener(mockListener);
        mService.prepareRebootEscrow();

        clearInvocations(mServiceConnection);
        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
        verify(mockListener).onPreparedForReboot(eq(true));
        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());

        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
                .thenAnswer(invocation -> invocation.getArgument(0));
        assertTrue(mService.armRebootEscrowIfNeeded());
        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());

        assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
        assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
        assertTrue(mStorage.hasRebootEscrowServerBlob());
    }

    @Test
    public void armService_HalFailure_NonFatal() throws Exception {
        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -345,6 +407,40 @@ public class RebootEscrowManagerTests {
        verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
    }

    @Test
    public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception {
        setServerBasedRebootEscrowProvider();

        when(mInjected.getBootCount()).thenReturn(0);
        RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
        mService.setRebootEscrowListener(mockListener);
        mService.prepareRebootEscrow();

        clearInvocations(mServiceConnection);
        mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
        verify(mockListener).onPreparedForReboot(eq(true));
        verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());

        // Use x -> x for both wrap & unwrap functions.
        when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
                .thenAnswer(invocation -> invocation.getArgument(0));
        assertTrue(mService.armRebootEscrowIfNeeded());
        verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
        assertTrue(mStorage.hasRebootEscrowServerBlob());

        // pretend reboot happens here
        when(mInjected.getBootCount()).thenReturn(1);
        ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
        doNothing().when(mInjected).reportMetric(metricsSuccessCaptor.capture());

        when(mServiceConnection.unwrap(any(), anyLong()))
                .thenAnswer(invocation -> invocation.getArgument(0));
        mService.loadRebootEscrowDataIfAvailable();
        verify(mServiceConnection).unwrap(any(), anyLong());
        assertTrue(metricsSuccessCaptor.getValue());
        verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
    }

    @Test
    public void loadRebootEscrowDataIfAvailable_TooManyBootsInBetween_NoMetrics() throws Exception {
        when(mInjected.getBootCount()).thenReturn(0);
Loading