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

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

Merge changes Id0e18bef,Ie2b5f559 am: 5bf384ca am: e5d8f603

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

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Idc61336580ca8b58efad146c5aebfcbbc845e78d
parents 71badc9b e5d8f603
Loading
Loading
Loading
Loading
+109 −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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;

class AesEncryptionUtil {
    /** The algorithm used for the encryption of the key blob. */
    private static final String CIPHER_ALGO = "AES/GCM/NoPadding";

    private AesEncryptionUtil() {}

    static byte[] decrypt(SecretKey key, DataInputStream cipherStream) throws IOException {
        Objects.requireNonNull(key);
        Objects.requireNonNull(cipherStream);

        int ivSize = cipherStream.readInt();
        if (ivSize < 0 || ivSize > 32) {
            throw new IOException("IV out of range: " + ivSize);
        }
        byte[] iv = new byte[ivSize];
        cipherStream.readFully(iv);

        int rawCipherTextSize = cipherStream.readInt();
        if (rawCipherTextSize < 0) {
            throw new IOException("Invalid cipher text size: " + rawCipherTextSize);
        }

        byte[] rawCipherText = new byte[rawCipherTextSize];
        cipherStream.readFully(rawCipherText);

        final byte[] plainText;
        try {
            Cipher c = Cipher.getInstance(CIPHER_ALGO);
            c.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
            plainText = c.doFinal(rawCipherText);
        } catch (NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
                | IllegalBlockSizeException | NoSuchPaddingException
                | InvalidAlgorithmParameterException e) {
            throw new IOException("Could not decrypt cipher text", e);
        }

        return plainText;
    }

    static byte[] decrypt(SecretKey key, byte[] cipherText) throws IOException {
        Objects.requireNonNull(key);
        Objects.requireNonNull(cipherText);

        DataInputStream cipherStream = new DataInputStream(new ByteArrayInputStream(cipherText));
        return decrypt(key, cipherStream);
    }

    static byte[] encrypt(SecretKey key, byte[] plainText) throws IOException {
        Objects.requireNonNull(key);
        Objects.requireNonNull(plainText);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);

        final byte[] cipherText;
        final byte[] iv;
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_ALGO);
            cipher.init(Cipher.ENCRYPT_MODE, key);
            cipherText = cipher.doFinal(plainText);
            iv = cipher.getIV();
        } catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException
                | NoSuchPaddingException | InvalidKeyException e) {
            throw new IOException("Could not encrypt input data", e);
        }

        dos.writeInt(iv.length);
        dos.write(iv);
        dos.writeInt(cipherText.length);
        dos.write(cipherText);

        return bos.toByteArray();
    }
}
+22 −72
Original line number Diff line number Diff line
@@ -16,22 +16,14 @@

package com.android.server.locksettings;

import com.android.internal.util.Preconditions;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.SecretKey;

/**
 * Holds the data necessary to complete a reboot escrow of the Synthetic Password.
@@ -41,22 +33,17 @@ class RebootEscrowData {
     * This is the current version of the escrow data format. This should be incremented if the
     * format on disk is changed.
     */
    private static final int CURRENT_VERSION = 1;

    /** The algorithm used for the encryption of the key blob. */
    private static final String CIPHER_ALGO = "AES/GCM/NoPadding";
    private static final int CURRENT_VERSION = 2;

    private RebootEscrowData(byte spVersion, byte[] iv, byte[] syntheticPassword, byte[] blob,
    private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob,
            RebootEscrowKey key) {
        mSpVersion = spVersion;
        mIv = iv;
        mSyntheticPassword = syntheticPassword;
        mBlob = blob;
        mKey = key;
    }

    private final byte mSpVersion;
    private final byte[] mIv;
    private final byte[] mSyntheticPassword;
    private final byte[] mBlob;
    private final RebootEscrowKey mKey;
@@ -65,10 +52,6 @@ class RebootEscrowData {
        return mSpVersion;
    }

    public byte[] getIv() {
        return mIv;
    }

    public byte[] getSyntheticPassword() {
        return mSyntheticPassword;
    }
@@ -81,76 +64,43 @@ class RebootEscrowData {
        return mKey;
    }

    static RebootEscrowData fromEncryptedData(RebootEscrowKey key, byte[] blob)
    static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk)
            throws IOException {
        Preconditions.checkNotNull(key);
        Preconditions.checkNotNull(blob);
        Objects.requireNonNull(ks);
        Objects.requireNonNull(blob);

        DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
        int version = dis.readInt();
        if (version != CURRENT_VERSION) {
            throw new IOException("Unsupported version " + version);
        }

        byte spVersion = dis.readByte();

        int ivSize = dis.readInt();
        if (ivSize < 0 || ivSize > 32) {
            throw new IOException("IV out of range: " + ivSize);
        }
        byte[] iv = new byte[ivSize];
        dis.readFully(iv);
        // Decrypt the blob with the key from keystore first, then decrypt again with the reboot
        // escrow key.
        byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
        final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);

        int cipherTextSize = dis.readInt();
        if (cipherTextSize < 0) {
            throw new IOException("Invalid cipher text size: " + cipherTextSize);
        return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
    }

        byte[] cipherText = new byte[cipherTextSize];
        dis.readFully(cipherText);

        final byte[] syntheticPassword;
        try {
            Cipher c = Cipher.getInstance(CIPHER_ALGO);
            c.init(Cipher.DECRYPT_MODE, key.getKey(), new IvParameterSpec(iv));
            syntheticPassword = c.doFinal(cipherText);
        } catch (NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
                | IllegalBlockSizeException | NoSuchPaddingException
                | InvalidAlgorithmParameterException e) {
            throw new IOException("Could not decrypt ciphertext", e);
        }

        return new RebootEscrowData(spVersion, iv, syntheticPassword, blob, key);
    }

    static RebootEscrowData fromSyntheticPassword(RebootEscrowKey key, byte spVersion,
            byte[] syntheticPassword)
    static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion,
            byte[] syntheticPassword, SecretKey kk)
            throws IOException {
        Preconditions.checkNotNull(syntheticPassword);
        Objects.requireNonNull(syntheticPassword);

        // Encrypt synthetic password with the escrow key first; then encrypt the blob again with
        // the key from keystore.
        byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(ks.getKey(), syntheticPassword);
        byte[] kkEncryptedBlob = AesEncryptionUtil.encrypt(kk, ksEncryptedBlob);

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);

        final byte[] cipherText;
        final byte[] iv;
        try {
            Cipher cipher = Cipher.getInstance(CIPHER_ALGO);
            cipher.init(Cipher.ENCRYPT_MODE, key.getKey());
            cipherText = cipher.doFinal(syntheticPassword);
            iv = cipher.getIV();
        } catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException
                | NoSuchPaddingException | InvalidKeyException e) {
            throw new IOException("Could not encrypt reboot escrow data", e);
        }

        dos.writeInt(CURRENT_VERSION);
        dos.writeByte(spVersion);
        dos.writeInt(iv.length);
        dos.write(iv);
        dos.writeInt(cipherText.length);
        dos.write(cipherText);
        dos.write(kkEncryptedBlob);

        return new RebootEscrowData(spVersion, iv, syntheticPassword, bos.toByteArray(),
                key);
        return new RebootEscrowData(spVersion, syntheticPassword, bos.toByteArray(), ks);
    }
}
+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;
        }
    }
}
+52 −11
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, kk);

            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,11 +297,16 @@ 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.
            escrowData = RebootEscrowData.fromSyntheticPassword(escrowKey, spVersion,
                    syntheticPassword);
                    syntheticPassword, kk);
        } catch (IOException e) {
            setRebootEscrowReady(false);
            Slog.w(TAG, "Could not escrow reboot data", e);
@@ -348,7 +383,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);
+43 −8

File changed.

Preview size limit exceeded, changes collapsed.

Loading