diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java index 38eeb88e63b01732a62cbeba84b298256eab0439..af0774c6c3fa79dfa8195094fab511780da85530 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowData.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java @@ -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,17 +90,20 @@ 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); - - return new RebootEscrowData(spVersion, syntheticPassword, blob, ks); + 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, diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java index 53b62ca6ecb5daaf2692fad4655a6f84b3297e14..30ea5556b41c03d4c92fc317774135cedab440f2 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java @@ -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; } diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java index 9d09637cdc74b9126800155d4a04f28a42724c7a..b3b45460899d1a0dcbea6b73ceeb836835f28d4d 100644 --- a/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java +++ b/services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java @@ -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 { diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java index 32445fd1a47dc6b3c7d9bcec589f158e77bf0271..2eedc3251daad09f1b9703e856f998b0d51bc937 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java @@ -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)); + } }