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

Commit 8d380420 authored by Tianjie Xu's avatar Tianjie Xu Committed by Gerrit Code Review
Browse files

Merge "Support parsing legacy reboot escrow data"

parents b6ce95e8 44f3adc9
Loading
Loading
Loading
Loading
+32 −10
Original line number Diff line number Diff line
@@ -35,6 +35,12 @@ class RebootEscrowData {
     */
    private static final int CURRENT_VERSION = 2;

    /**
    * This is the legacy version of the escrow data format for R builds. The escrow data is only
    * encrypted by the escrow key, without additional wrap of another key from keystore.
    */
    private static final int LEGACY_SINGLE_ENCRYPTED_VERSION = 1;

    private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob,
            RebootEscrowKey key) {
        mSpVersion = spVersion;
@@ -64,6 +70,19 @@ class RebootEscrowData {
        return mKey;
    }

    private static byte[] decryptBlobCurrentVersion(SecretKey kk, RebootEscrowKey ks,
            DataInputStream dis) throws IOException {
        if (kk == null) {
            throw new IOException("Failed to find wrapper key in keystore, cannot decrypt the"
                    + " escrow data");
        }

        // Decrypt the blob with the key from keystore first, then decrypt again with the reboot
        // escrow key.
        byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
        return AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
    }

    static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk)
            throws IOException {
        Objects.requireNonNull(ks);
@@ -71,18 +90,21 @@ class RebootEscrowData {

        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();

        // 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);

        switch (version) {
            case CURRENT_VERSION: {
                byte[] syntheticPassword = decryptBlobCurrentVersion(kk, ks, dis);
                return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
            }
            case LEGACY_SINGLE_ENCRYPTED_VERSION: {
                // Decrypt the blob with the escrow key directly.
                byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), dis);
                return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
            }
            default:
                throw new IOException("Unsupported version " + version);
        }
    }

    static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion,
            byte[] syntheticPassword, SecretKey kk)
+6 −1
Original line number Diff line number Diff line
@@ -146,6 +146,7 @@ class RebootEscrowManager {
            RebootEscrowProviderInterface rebootEscrowProvider;
            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
                    "server_based_ror_enabled", false)) {
                Slog.i(TAG, "Using server based resume on reboot");
                rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage);
            } else {
                rebootEscrowProvider = new RebootEscrowProviderHalImpl();
@@ -272,6 +273,10 @@ class RebootEscrowManager {
        // generated before reboot. Note that we will clear the escrow key even if the keystore key
        // is null.
        SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
        if (kk == null) {
            Slog.i(TAG, "Failed to load the key for resume on reboot from key store.");
        }

        RebootEscrowKey escrowKey;
        try {
            escrowKey = getAndClearRebootEscrowKey(kk);
@@ -281,7 +286,7 @@ class RebootEscrowManager {
            return;
        }

        if (kk == null || escrowKey == null) {
        if (escrowKey == null) {
            onGetRebootEscrowKeyFailed(users);
            return;
        }
+5 −0
Original line number Diff line number Diff line
@@ -136,6 +136,11 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa
            Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
            return null;
        }
        if (decryptionKey == null) {
            Slog.w(TAG, "Failed to decrypt the escrow key; decryption key from keystore is"
                    + " null.");
            return null;
        }

        Slog.i(TAG, "Loaded reboot escrow server blob from storage");
        try {
+30 −17
Original line number Diff line number Diff line
@@ -19,19 +19,17 @@ package com.android.server.locksettings;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;

import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;

import androidx.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.security.GeneralSecurityException;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;

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

/**
 * atest FrameworksServicesTests:RebootEscrowDataTest
@@ -41,22 +39,18 @@ public class RebootEscrowDataTest {
    private RebootEscrowKey mKey;
    private SecretKey mKeyStoreEncryptionKey;

    private SecretKey generateNewRebootEscrowEncryptionKey() throws GeneralSecurityException {
        KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
        generator.init(new KeyGenParameterSpec.Builder(
                "reboot_escrow_data_test_key",
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setKeySize(256)
                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                .build());
        return generator.generateKey();
    }
    // Hex encoding of a randomly generated AES key for test.
    private static final byte[] TEST_AES_KEY = new byte[] {
            0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61,
            0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31,
            0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09,
            0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
    };

    @Before
    public void generateKey() throws Exception {
        mKey = RebootEscrowKey.generate();
        mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey();
        mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES");
    }

    private static byte[] getTestSp() {
@@ -114,4 +108,23 @@ public class RebootEscrowDataTest {
        assertThat(decrypted, is(testSp));
    }

    @Test
    public void fromEncryptedData_legacyVersion_success() throws Exception {
        byte[] testSp = getTestSp();
        byte[] ksEncryptedBlob = AesEncryptionUtil.encrypt(mKey.getKey(), testSp);

        // Write a legacy blob encrypted only by k_s.
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(bos);
        dos.writeInt(1);
        dos.writeByte(3);
        dos.write(ksEncryptedBlob);
        byte[] legacyBlob = bos.toByteArray();

        RebootEscrowData actual = RebootEscrowData.fromEncryptedData(mKey, legacyBlob, null);

        assertThat(actual.getSpVersion(), is((byte) 3));
        assertThat(actual.getKey().getKeyBytes(), is(mKey.getKeyBytes()));
        assertThat(actual.getSyntheticPassword(), is(testSp));
    }
}