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;
|
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,
|
private RebootEscrowData(byte spVersion, byte[] syntheticPassword, byte[] blob,
|
||||||
RebootEscrowKey key) {
|
RebootEscrowKey key) {
|
||||||
mSpVersion = spVersion;
|
mSpVersion = spVersion;
|
||||||
@@ -64,6 +70,19 @@ class RebootEscrowData {
|
|||||||
return mKey;
|
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)
|
static RebootEscrowData fromEncryptedData(RebootEscrowKey ks, byte[] blob, SecretKey kk)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Objects.requireNonNull(ks);
|
Objects.requireNonNull(ks);
|
||||||
@@ -71,17 +90,20 @@ class RebootEscrowData {
|
|||||||
|
|
||||||
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
|
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
|
||||||
int version = dis.readInt();
|
int version = dis.readInt();
|
||||||
if (version != CURRENT_VERSION) {
|
|
||||||
throw new IOException("Unsupported version " + version);
|
|
||||||
}
|
|
||||||
byte spVersion = dis.readByte();
|
byte spVersion = dis.readByte();
|
||||||
|
switch (version) {
|
||||||
// Decrypt the blob with the key from keystore first, then decrypt again with the reboot
|
case CURRENT_VERSION: {
|
||||||
// escrow key.
|
byte[] syntheticPassword = decryptBlobCurrentVersion(kk, ks, dis);
|
||||||
byte[] ksEncryptedBlob = AesEncryptionUtil.decrypt(kk, dis);
|
return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
|
||||||
final byte[] syntheticPassword = AesEncryptionUtil.decrypt(ks.getKey(), ksEncryptedBlob);
|
}
|
||||||
|
case LEGACY_SINGLE_ENCRYPTED_VERSION: {
|
||||||
return new RebootEscrowData(spVersion, syntheticPassword, blob, ks);
|
// 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,
|
static RebootEscrowData fromSyntheticPassword(RebootEscrowKey ks, byte spVersion,
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ class RebootEscrowManager {
|
|||||||
RebootEscrowProviderInterface rebootEscrowProvider;
|
RebootEscrowProviderInterface rebootEscrowProvider;
|
||||||
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
|
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_OTA,
|
||||||
"server_based_ror_enabled", false)) {
|
"server_based_ror_enabled", false)) {
|
||||||
|
Slog.i(TAG, "Using server based resume on reboot");
|
||||||
rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage);
|
rebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(mContext, mStorage);
|
||||||
} else {
|
} else {
|
||||||
rebootEscrowProvider = new RebootEscrowProviderHalImpl();
|
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
|
// generated before reboot. Note that we will clear the escrow key even if the keystore key
|
||||||
// is null.
|
// is null.
|
||||||
SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
|
SecretKey kk = mKeyStoreManager.getKeyStoreEncryptionKey();
|
||||||
|
if (kk == null) {
|
||||||
|
Slog.i(TAG, "Failed to load the key for resume on reboot from key store.");
|
||||||
|
}
|
||||||
|
|
||||||
RebootEscrowKey escrowKey;
|
RebootEscrowKey escrowKey;
|
||||||
try {
|
try {
|
||||||
escrowKey = getAndClearRebootEscrowKey(kk);
|
escrowKey = getAndClearRebootEscrowKey(kk);
|
||||||
@@ -281,7 +286,7 @@ class RebootEscrowManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kk == null || escrowKey == null) {
|
if (escrowKey == null) {
|
||||||
onGetRebootEscrowKeyFailed(users);
|
onGetRebootEscrowKeyFailed(users);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,11 @@ class RebootEscrowProviderServerBasedImpl implements RebootEscrowProviderInterfa
|
|||||||
Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
|
Slog.w(TAG, "Failed to read reboot escrow server blob from storage");
|
||||||
return null;
|
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");
|
Slog.i(TAG, "Loaded reboot escrow server blob from storage");
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -19,19 +19,17 @@ package com.android.server.locksettings;
|
|||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
import android.security.keystore.KeyGenParameterSpec;
|
|
||||||
import android.security.keystore.KeyProperties;
|
|
||||||
|
|
||||||
import androidx.test.runner.AndroidJUnit4;
|
import androidx.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
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.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* atest FrameworksServicesTests:RebootEscrowDataTest
|
* atest FrameworksServicesTests:RebootEscrowDataTest
|
||||||
@@ -41,22 +39,18 @@ public class RebootEscrowDataTest {
|
|||||||
private RebootEscrowKey mKey;
|
private RebootEscrowKey mKey;
|
||||||
private SecretKey mKeyStoreEncryptionKey;
|
private SecretKey mKeyStoreEncryptionKey;
|
||||||
|
|
||||||
private SecretKey generateNewRebootEscrowEncryptionKey() throws GeneralSecurityException {
|
// Hex encoding of a randomly generated AES key for test.
|
||||||
KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES);
|
private static final byte[] TEST_AES_KEY = new byte[] {
|
||||||
generator.init(new KeyGenParameterSpec.Builder(
|
0x44, 0x74, 0x61, 0x54, 0x29, 0x74, 0x37, 0x61,
|
||||||
"reboot_escrow_data_test_key",
|
0x48, 0x19, 0x12, 0x54, 0x13, 0x13, 0x52, 0x31,
|
||||||
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
0x70, 0x70, 0x75, 0x25, 0x27, 0x31, 0x49, 0x09,
|
||||||
.setKeySize(256)
|
0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
|
||||||
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
|
};
|
||||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
|
|
||||||
.build());
|
|
||||||
return generator.generateKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void generateKey() throws Exception {
|
public void generateKey() throws Exception {
|
||||||
mKey = RebootEscrowKey.generate();
|
mKey = RebootEscrowKey.generate();
|
||||||
mKeyStoreEncryptionKey = generateNewRebootEscrowEncryptionKey();
|
mKeyStoreEncryptionKey = new SecretKeySpec(TEST_AES_KEY, "AES");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getTestSp() {
|
private static byte[] getTestSp() {
|
||||||
@@ -114,4 +108,23 @@ public class RebootEscrowDataTest {
|
|||||||
assertThat(decrypted, is(testSp));
|
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