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

Commit 5d33c465 authored by Tianjie's avatar Tianjie
Browse files

Load and generate key from keystore in reboot escrow

According to the design, we need to wrap both reboot escrow key
and reboot escrow data with an additional key from android
keystore. The new key in keystore is created upon a new resume on
reboot request, and cleared after decryption is attempted at boot
time.

Bug: 172780686
Test: atest FrameworksServicesTests:RebootEscrowDataTest \
            FrameworksServicesTests:LockSettingsServiceTests \
            FrameworksServicesTests:RecoverySystemServiceTest \
            FrameworksServicesTests:RebootEscrowManagerTests

Change-Id: Ie2b5f559fb5d3217337e31b0dff507b98715384d
parent a8cc70b5
Loading
Loading
Loading
Loading
+134 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.AndroidKeyStoreSpi;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore2.AndroidKeyStoreLoadStoreParameter;
import android.security.keystore2.AndroidKeyStoreProvider;
import android.util.Slog;

import com.android.internal.annotations.GuardedBy;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

/**
 * This class loads and generates the key used for resume on reboot from android keystore.
 */
public class RebootEscrowKeyStoreManager {
    private static final String TAG = "RebootEscrowKeyStoreManager";

    /**
     * The key alias in keystore. This key is used to wrap both escrow key and escrow data.
     */
    public static final String REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME =
            "reboot_escrow_key_store_encryption_key";

    public static final int KEY_LENGTH = 256;

    /**
     * Use keystore2 once it's installed.
     */
    private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeystore";

    /**
     * The selinux namespace for resume_on_reboot_key
     */
    private static final int KEY_STORE_NAMESPACE = 120;

    /**
     * Hold this lock when getting or generating the encryption key in keystore.
     */
    private final Object mKeyStoreLock = new Object();

    @GuardedBy("mKeyStoreLock")
    private SecretKey getKeyStoreEncryptionKeyLocked() {
        try {
            KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
            KeyStore.LoadStoreParameter loadStoreParameter = null;
            // Load from the specific namespace if keystore2 is enabled.
            if (AndroidKeyStoreProvider.isInstalled()) {
                loadStoreParameter = new AndroidKeyStoreLoadStoreParameter(KEY_STORE_NAMESPACE);
            }
            keyStore.load(loadStoreParameter);
            return (SecretKey) keyStore.getKey(REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME,
                    null);
        } catch (IOException | GeneralSecurityException e) {
            Slog.e(TAG, "Unable to get encryption key from keystore.", e);
        }
        return null;
    }

    protected SecretKey getKeyStoreEncryptionKey() {
        synchronized (mKeyStoreLock) {
            return getKeyStoreEncryptionKeyLocked();
        }
    }

    protected void clearKeyStoreEncryptionKey() {
        synchronized (mKeyStoreLock) {
            try {
                KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
                KeyStore.LoadStoreParameter loadStoreParameter = null;
                // Load from the specific namespace if keystore2 is enabled.
                if (AndroidKeyStoreProvider.isInstalled()) {
                    loadStoreParameter = new AndroidKeyStoreLoadStoreParameter(KEY_STORE_NAMESPACE);
                }
                keyStore.load(loadStoreParameter);
                keyStore.deleteEntry(REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME);
            } catch (IOException | GeneralSecurityException e) {
                Slog.e(TAG, "Unable to delete encryption key in keystore.", e);
            }
        }
    }

    protected SecretKey generateKeyStoreEncryptionKeyIfNeeded() {
        synchronized (mKeyStoreLock) {
            SecretKey kk = getKeyStoreEncryptionKeyLocked();
            if (kk != null) {
                return kk;
            }

            try {
                KeyGenerator generator = KeyGenerator.getInstance(
                        KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStoreSpi.NAME);
                KeyGenParameterSpec.Builder parameterSpecBuilder = new KeyGenParameterSpec.Builder(
                        REBOOT_ESCROW_KEY_STORE_ENCRYPTION_KEY_NAME,
                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                        .setKeySize(KEY_LENGTH)
                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
                // Generate the key with the correct namespace if keystore2 is enabled.
                if (AndroidKeyStoreProvider.isInstalled()) {
                    parameterSpecBuilder.setNamespace(KEY_STORE_NAMESPACE);
                }
                generator.init(parameterSpecBuilder.build());
                return generator.generateKey();
            } catch (GeneralSecurityException e) {
                // Should never happen.
                Slog.e(TAG, "Unable to generate key from keystore.", e);
            }
            return null;
        }
    }
}
+51 −9
Original line number Diff line number Diff line
@@ -40,6 +40,18 @@ import java.util.Date;
import java.util.List;
import java.util.Locale;

import javax.crypto.SecretKey;

/**
 * This class aims to persists the synthetic password(SP) across reboot in a secure way. In
 * particular, it manages the encryption of the sp before reboot, and decryption of the sp after
 * reboot. Here are the meaning of some terms.
 *   SP: synthetic password
 *   K_s: The RebootEscrowKey, i.e. AES-GCM key stored in memory
 *   K_k: AES-GCM key in android keystore
 *   RebootEscrowData: The synthetic password and its encrypted blob. We encrypt SP with K_s first,
 *      then with K_k, i.e. E(K_k, E(K_s, SP))
 */
class RebootEscrowManager {
    private static final String TAG = "RebootEscrowManager";

@@ -101,6 +113,8 @@ class RebootEscrowManager {

    private final Callbacks mCallbacks;

    private final RebootEscrowKeyStoreManager mKeyStoreManager;

    interface Callbacks {
        boolean isUserSecure(int userId);

@@ -109,11 +123,13 @@ class RebootEscrowManager {

    static class Injector {
        protected Context mContext;

        private final RebootEscrowKeyStoreManager mKeyStoreManager;
        private final RebootEscrowProviderInterface mRebootEscrowProvider;

        Injector(Context context) {
            mContext = context;
            mKeyStoreManager = new RebootEscrowKeyStoreManager();

            RebootEscrowProviderInterface rebootEscrowProvider = null;
            // TODO(xunchang) add implementation for server based ror.
            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
@@ -138,6 +154,10 @@ class RebootEscrowManager {
            return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
        }

        public RebootEscrowKeyStoreManager getKeyStoreManager() {
            return mKeyStoreManager;
        }

        public RebootEscrowProviderInterface getRebootEscrowProvider() {
            return mRebootEscrowProvider;
        }
@@ -168,6 +188,7 @@ class RebootEscrowManager {
        mStorage = storage;
        mUserManager = injector.getUserManager();
        mEventLog = injector.getEventLog();
        mKeyStoreManager = injector.getKeyStoreManager();
    }

    void loadRebootEscrowDataIfAvailable() {
@@ -183,8 +204,12 @@ class RebootEscrowManager {
            return;
        }

        RebootEscrowKey escrowKey = getAndClearRebootEscrowKey();
        if (escrowKey == null) {
        // Fetch the key from keystore to decrypt the escrow data & escrow key; this key is
        // generated before reboot. Note that we will clear the escrow key even if the keystore key
        // is null.
        SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
        RebootEscrowKey escrowKey = getAndClearRebootEscrowKey(kk);
        if (kk == null || escrowKey == null) {
            Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
            for (UserInfo user : users) {
                mStorage.removeRebootEscrow(user.id);
@@ -197,7 +222,7 @@ class RebootEscrowManager {

        boolean allUsersUnlocked = true;
        for (UserInfo user : rebootEscrowUsers) {
            allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
            allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey, kk);
        }
        onEscrowRestoreComplete(allUsersUnlocked);
    }
@@ -212,7 +237,7 @@ class RebootEscrowManager {
        }
    }

    private RebootEscrowKey getAndClearRebootEscrowKey() {
    private RebootEscrowKey getAndClearRebootEscrowKey(SecretKey kk) {
        RebootEscrowProviderInterface rebootEscrowProvider = mInjector.getRebootEscrowProvider();
        if (rebootEscrowProvider == null) {
            Slog.w(TAG,
@@ -220,14 +245,16 @@ class RebootEscrowManager {
            return null;
        }

        RebootEscrowKey key = rebootEscrowProvider.getAndClearRebootEscrowKey(null);
        // The K_s blob maybe encrypted by K_k as well.
        RebootEscrowKey key = rebootEscrowProvider.getAndClearRebootEscrowKey(kk);
        if (key != null) {
            mEventLog.addEntry(RebootEscrowEvent.RETRIEVED_STORED_KEK);
        }
        return key;
    }

    private boolean restoreRebootEscrowForUser(@UserIdInt int userId, RebootEscrowKey key) {
    private boolean restoreRebootEscrowForUser(@UserIdInt int userId, RebootEscrowKey ks,
            SecretKey kk) {
        if (!mStorage.hasRebootEscrow(userId)) {
            return false;
        }
@@ -236,7 +263,7 @@ class RebootEscrowManager {
            byte[] blob = mStorage.readRebootEscrow(userId);
            mStorage.removeRebootEscrow(userId);

            RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(key, blob);
            RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(ks, blob);

            mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
                    escrowData.getSyntheticPassword(), userId);
@@ -246,6 +273,9 @@ class RebootEscrowManager {
        } catch (IOException e) {
            Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
            return false;
        } finally {
            // Clear the old key in keystore. A new key will be generated by new RoR requests.
            mKeyStoreManager.clearKeyStoreEncryptionKey();
        }
    }

@@ -267,6 +297,12 @@ class RebootEscrowManager {
            return;
        }

        SecretKey kk = mKeyStoreManager.generateKeyStoreEncryptionKeyIfNeeded();
        if (kk == null) {
            Slog.e(TAG, "Failed to generate encryption key from keystore.");
            return;
        }

        final RebootEscrowData escrowData;
        try {
            // TODO(xunchang) further wrap the escrowData with a key from keystore.
@@ -348,7 +384,13 @@ class RebootEscrowManager {
            return false;
        }

        boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, null);
        // We will use the same key from keystore to encrypt the escrow key and escrow data blob.
        SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
        if (kk == null) {
            Slog.e(TAG, "Failed to get encryption key from keystore.");
            return false;
        }
        boolean armedRebootEscrow = rebootEscrowProvider.storeRebootEscrowKey(escrowKey, kk);
        if (armedRebootEscrow) {
            mStorage.setInt(REBOOT_ESCROW_ARMED_KEY, mInjector.getBootCount(), USER_SYSTEM);
            mEventLog.addEntry(RebootEscrowEvent.SET_ARMED_STATUS);
+30 −1
Original line number Diff line number Diff line
@@ -61,6 +61,9 @@ import org.mockito.ArgumentCaptor;
import java.io.File;
import java.util.ArrayList;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
@@ -77,15 +80,25 @@ public class RebootEscrowManagerTests {
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
    };

    // Hex encoding of a randomly generated AES key for test.
    private static final byte[] TEST_AES_KEY = new byte[] {
            0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31,
            0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61,
            0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09,
            0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
    };

    private Context mContext;
    private UserManager mUserManager;
    private RebootEscrowManager.Callbacks mCallbacks;
    private IRebootEscrow mRebootEscrow;
    private RebootEscrowKeyStoreManager mKeyStoreManager;

    LockSettingsStorageTestable mStorage;

    private MockableRebootEscrowInjected mInjected;
    private RebootEscrowManager mService;
    private SecretKey mAesKey;

    public interface MockableRebootEscrowInjected {
        int getBootCount();
@@ -98,9 +111,11 @@ public class RebootEscrowManagerTests {
        private final RebootEscrowProviderInterface mRebootEscrowProvider;
        private final UserManager mUserManager;
        private final MockableRebootEscrowInjected mInjected;
        private final RebootEscrowKeyStoreManager mKeyStoreManager;

        MockInjector(Context context, UserManager userManager,
                IRebootEscrow rebootEscrow,
                RebootEscrowKeyStoreManager keyStoreManager,
                MockableRebootEscrowInjected injected) {
            super(context);
            mRebootEscrow = rebootEscrow;
@@ -114,6 +129,7 @@ public class RebootEscrowManagerTests {
                    };
            mRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector);
            mUserManager = userManager;
            mKeyStoreManager = keyStoreManager;
            mInjected = injected;
        }

@@ -127,6 +143,11 @@ public class RebootEscrowManagerTests {
            return mRebootEscrowProvider;
        }

        @Override
        public RebootEscrowKeyStoreManager getKeyStoreManager() {
            return mKeyStoreManager;
        }

        @Override
        public int getBootCount() {
            return mInjected.getBootCount();
@@ -144,6 +165,11 @@ public class RebootEscrowManagerTests {
        mUserManager = mock(UserManager.class);
        mCallbacks = mock(RebootEscrowManager.Callbacks.class);
        mRebootEscrow = mock(IRebootEscrow.class);
        mKeyStoreManager = mock(RebootEscrowKeyStoreManager.class);
        mAesKey = new SecretKeySpec(TEST_AES_KEY, "AES");

        when(mKeyStoreManager.getKeyStoreEncryptionKey()).thenReturn(mAesKey);
        when(mKeyStoreManager.generateKeyStoreEncryptionKeyIfNeeded()).thenReturn(mAesKey);

        mStorage = new LockSettingsStorageTestable(mContext,
                new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
@@ -160,7 +186,7 @@ public class RebootEscrowManagerTests {
        when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
        mInjected = mock(MockableRebootEscrowInjected.class);
        mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow,
                mInjected), mCallbacks, mStorage);
                mKeyStoreManager, mInjected), mCallbacks, mStorage);
    }

    @Test
@@ -213,6 +239,7 @@ public class RebootEscrowManagerTests {
        assertNotNull(
                mStorage.getString(RebootEscrowManager.REBOOT_ESCROW_ARMED_KEY, null, USER_SYSTEM));
        verify(mRebootEscrow).storeKey(any());
        verify(mKeyStoreManager).getKeyStoreEncryptionKey();

        assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
        assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
@@ -300,6 +327,7 @@ public class RebootEscrowManagerTests {
        ArgumentCaptor<byte[]> keyByteCaptor = ArgumentCaptor.forClass(byte[].class);
        assertTrue(mService.armRebootEscrowIfNeeded());
        verify(mRebootEscrow).storeKey(keyByteCaptor.capture());
        verify(mKeyStoreManager).getKeyStoreEncryptionKey();

        assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
        assertFalse(mStorage.hasRebootEscrow(NONSECURE_SECONDARY_USER_ID));
@@ -314,6 +342,7 @@ public class RebootEscrowManagerTests {
        mService.loadRebootEscrowDataIfAvailable();
        verify(mRebootEscrow).retrieveKey();
        assertTrue(metricsSuccessCaptor.getValue());
        verify(mKeyStoreManager).clearKeyStoreEncryptionKey();
    }

    @Test