Merge "Support parsing legacy reboot escrow data"
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user