Loading services/core/java/com/android/server/locksettings/RebootEscrowData.java +32 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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) Loading services/core/java/com/android/server/locksettings/RebootEscrowManager.java +6 −1 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -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); Loading @@ -281,7 +286,7 @@ class RebootEscrowManager { return; } if (kk == null || escrowKey == null) { if (escrowKey == null) { onGetRebootEscrowKeyFailed(users); return; } Loading services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java +5 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java +30 −17 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() { Loading Loading @@ -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)); } } Loading
services/core/java/com/android/server/locksettings/RebootEscrowData.java +32 −10 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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) Loading
services/core/java/com/android/server/locksettings/RebootEscrowManager.java +6 −1 Original line number Diff line number Diff line Loading @@ -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(); Loading Loading @@ -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); Loading @@ -281,7 +286,7 @@ class RebootEscrowManager { return; } if (kk == null || escrowKey == null) { if (escrowKey == null) { onGetRebootEscrowKeyFailed(users); return; } Loading
services/core/java/com/android/server/locksettings/RebootEscrowProviderServerBasedImpl.java +5 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading
services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java +30 −17 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() { Loading Loading @@ -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)); } }