Route EncryptedLocalTransport KV backup/restore through encryption code

Bug: 142227548
Test: Verify the device boots successfully
      Verify EncryptedLocalTransport APK is present
      Verify manual backup/restore using bmgr for LocalTransport and EncryptedLocalTransport
      For LocalTransport (unencrypted) and EncryptedLocalTransport:
        atest CtsBackupTestCases
        atest CtsBackupHostTestCases
        atest GtsBackupTestCases
        atest GtsBackupHostTestCases


Change-Id: Iac3a8a50d7f761442a4b784cfba3a980e900dd7f
This commit is contained in:
Ruslan Tkhakokhov
2019-10-07 14:40:40 +01:00
committed by Al Sutton
parent d0844929a3
commit 004e85f798
15 changed files with 763 additions and 47 deletions

View File

@@ -29,6 +29,8 @@
'service' attribute here is a flattened ComponentName string. -->
<backup-transport-whitelisted-service
service="com.android.localtransport/.LocalTransportService" />
<backup-transport-whitelisted-service
service="com.android.encryptedlocaltransport/.EncryptedLocalTransportService" />
<!-- Whitelist Shell to use the bugreport API -->
<bugreport-whitelisted package="com.android.shell" />

View File

@@ -17,8 +17,7 @@
android_app {
name: "BackupEncryption",
srcs: ["src/**/*.java"],
libs: ["backup-encryption-protos"],
static_libs: ["backuplib"],
static_libs: ["backup-encryption-protos", "backuplib"],
optimize: { enabled: false },
platform_apis: true,
certificate: "platform",

View File

@@ -0,0 +1,81 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.backup.encryption;
import android.content.Context;
import android.security.keystore.recovery.InternalRecoveryServiceException;
import android.security.keystore.recovery.RecoveryController;
import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
import com.android.server.backup.encryption.keys.TertiaryKeyManager;
import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
class EncryptionKeyHelper {
private static SecureRandom sSecureRandom = new SecureRandom();
private final Context mContext;
private final RecoverableKeyStoreSecondaryKeyManager
.RecoverableKeyStoreSecondaryKeyManagerProvider
mSecondaryKeyManagerProvider;
EncryptionKeyHelper(Context context) {
mContext = context;
mSecondaryKeyManagerProvider =
() ->
new RecoverableKeyStoreSecondaryKeyManager(
RecoveryController.getInstance(mContext), sSecureRandom);
}
RecoverableKeyStoreSecondaryKeyManager
.RecoverableKeyStoreSecondaryKeyManagerProvider getKeyManagerProvider() {
return mSecondaryKeyManagerProvider;
}
RecoverableKeyStoreSecondaryKey getActiveSecondaryKey()
throws UnrecoverableKeyException, InternalRecoveryServiceException {
String keyAlias = CryptoSettings.getInstance(mContext).getActiveSecondaryKeyAlias().get();
return mSecondaryKeyManagerProvider.get().get(keyAlias).get();
}
SecretKey getTertiaryKey(
String packageName,
RecoverableKeyStoreSecondaryKey secondaryKey)
throws IllegalBlockSizeException, InvalidAlgorithmParameterException,
NoSuchAlgorithmException, IOException, NoSuchPaddingException,
InvalidKeyException {
TertiaryKeyManager tertiaryKeyManager =
new TertiaryKeyManager(
mContext,
sSecureRandom,
TertiaryKeyRotationScheduler.getInstance(mContext),
secondaryKey,
packageName);
return tertiaryKeyManager.getKey();
}
}

View File

@@ -0,0 +1,143 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.backup.encryption;
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import com.android.server.backup.encryption.client.CryptoBackupServer;
import com.android.server.backup.encryption.keys.KeyWrapUtils;
import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey;
import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
import com.android.server.backup.encryption.tasks.EncryptedKvBackupTask;
import com.android.server.backup.encryption.tasks.EncryptedKvRestoreTask;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.Map;
public class KeyValueEncrypter {
private final Context mContext;
private final EncryptionKeyHelper mKeyHelper;
public KeyValueEncrypter(Context context) {
mContext = context;
mKeyHelper = new EncryptionKeyHelper(mContext);
}
public void encryptKeyValueData(
String packageName, ParcelFileDescriptor inputFd, OutputStream outputStream)
throws Exception {
EncryptedKvBackupTask.EncryptedKvBackupTaskFactory backupTaskFactory =
new EncryptedKvBackupTask.EncryptedKvBackupTaskFactory();
EncryptedKvBackupTask backupTask =
backupTaskFactory.newInstance(
mContext,
new SecureRandom(),
new FileBackupServer(outputStream),
CryptoSettings.getInstance(mContext),
mKeyHelper.getKeyManagerProvider(),
inputFd,
packageName);
backupTask.performBackup(/* incremental */ false);
}
public void decryptKeyValueData(String packageName,
InputStream encryptedInputStream, ParcelFileDescriptor outputFd) throws Exception {
RecoverableKeyStoreSecondaryKey secondaryKey = mKeyHelper.getActiveSecondaryKey();
EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory restoreTaskFactory =
new EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory();
EncryptedKvRestoreTask restoreTask =
restoreTaskFactory.newInstance(
mContext,
mKeyHelper.getKeyManagerProvider(),
new InputStreamFullRestoreDownloader(encryptedInputStream),
secondaryKey.getAlias(),
KeyWrapUtils.wrap(
secondaryKey.getSecretKey(),
mKeyHelper.getTertiaryKey(packageName, secondaryKey)));
restoreTask.getRestoreData(outputFd);
}
// TODO(b/142455725): Extract into a commong class.
private static class FileBackupServer implements CryptoBackupServer {
private static final String EMPTY_DOC_ID = "";
private final OutputStream mOutputStream;
FileBackupServer(OutputStream outputStream) {
mOutputStream = outputStream;
}
@Override
public String uploadIncrementalBackup(
String packageName,
String oldDocId,
byte[] diffScript,
WrappedKeyProto.WrappedKey tertiaryKey) {
throw new UnsupportedOperationException();
}
@Override
public String uploadNonIncrementalBackup(
String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) {
try {
mOutputStream.write(data);
} catch (IOException e) {
Log.w(TAG, "Failed to write encrypted data to file: ", e);
}
return EMPTY_DOC_ID;
}
@Override
public void setActiveSecondaryKeyAlias(
String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) {
// Do nothing.
}
}
// TODO(b/142455725): Extract into a commong class.
private static class InputStreamFullRestoreDownloader extends FullRestoreDownloader {
private final InputStream mInputStream;
InputStreamFullRestoreDownloader(InputStream inputStream) {
mInputStream = inputStream;
}
@Override
public int readNextChunk(byte[] buffer) throws IOException {
return mInputStream.read(buffer);
}
@Override
public void finish(FinishType finishType) {
try {
mInputStream.close();
} catch (IOException e) {
Log.w(TAG, "Error while reading restore data");
}
}
}
}

View File

@@ -18,27 +18,58 @@ package com.android.server.backup.encryption.transport;
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
import android.app.backup.BackupTransport;
import android.app.backup.RestoreDescription;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.encryption.KeyValueEncrypter;
import com.android.server.backup.transport.DelegatingTransport;
import com.android.server.backup.transport.TransportClient;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicReference;
/**
* This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
* sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
* TransportClient.connect(String)}.
*/
public class IntermediateEncryptingTransport extends DelegatingTransport {
private static final String BACKUP_TEMP_DIR = "backup";
private static final String RESTORE_TEMP_DIR = "restore";
private final TransportClient mTransportClient;
private final Object mConnectLock = new Object();
private final Context mContext;
private volatile IBackupTransport mRealTransport;
private AtomicReference<String> mNextRestorePackage = new AtomicReference<>();
private final KeyValueEncrypter mKeyValueEncrypter;
private final boolean mShouldEncrypt;
IntermediateEncryptingTransport(
TransportClient transportClient, Context context, boolean shouldEncrypt) {
this(transportClient, context, new KeyValueEncrypter(context), shouldEncrypt);
}
@VisibleForTesting
IntermediateEncryptingTransport(TransportClient transportClient) {
IntermediateEncryptingTransport(
TransportClient transportClient, Context context, KeyValueEncrypter keyValueEncrypter,
boolean shouldEncrypt) {
mTransportClient = transportClient;
mContext = context;
mKeyValueEncrypter = keyValueEncrypter;
mShouldEncrypt = shouldEncrypt;
}
@Override
@@ -46,9 +77,116 @@ public class IntermediateEncryptingTransport extends DelegatingTransport {
if (mRealTransport == null) {
connect();
}
Log.d(TAG, "real transport = " + mRealTransport.name());
return mRealTransport;
}
@Override
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
throws RemoteException {
if (!mShouldEncrypt) {
return super.performBackup(packageInfo, inFd, flags);
}
File encryptedStorageFile = getBackupTempStorage(packageInfo.packageName);
if (encryptedStorageFile == null) {
return BackupTransport.TRANSPORT_ERROR;
}
// Encrypt the backup data and write it into a temp file.
try (OutputStream encryptedOutput = new FileOutputStream(encryptedStorageFile)) {
mKeyValueEncrypter.encryptKeyValueData(packageInfo.packageName, inFd,
encryptedOutput);
} catch (Throwable e) {
Log.e(TAG, "Failed to encrypt backup data: ", e);
return BackupTransport.TRANSPORT_ERROR;
}
// Pass the temp file to the real transport for backup.
try (FileInputStream encryptedInput = new FileInputStream(encryptedStorageFile)) {
return super.performBackup(
packageInfo, ParcelFileDescriptor.dup(encryptedInput.getFD()), flags);
} catch (IOException e) {
Log.e(TAG, "Failed to read encrypted data from temp storage: ", e);
return BackupTransport.TRANSPORT_ERROR;
}
}
@Override
public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
if (!mShouldEncrypt) {
return super.getRestoreData(outFd);
}
String nextRestorePackage = mNextRestorePackage.get();
if (nextRestorePackage == null) {
Log.e(TAG, "No next restore package set");
return BackupTransport.TRANSPORT_ERROR;
}
File encryptedStorageFile = getRestoreTempStorage(nextRestorePackage);
if (encryptedStorageFile == null) {
return BackupTransport.TRANSPORT_ERROR;
}
// Get encrypted restore data from the real transport and write it into a temp file.
try (FileOutputStream outputStream = new FileOutputStream(encryptedStorageFile)) {
int status = super.getRestoreData(ParcelFileDescriptor.dup(outputStream.getFD()));
if (status != BackupTransport.TRANSPORT_OK) {
Log.e(TAG, "Failed to read restore data from transport, status = " + status);
return status;
}
} catch (IOException e) {
Log.e(TAG, "Failed to write encrypted data to temp storage: ", e);
return BackupTransport.TRANSPORT_ERROR;
}
// Decrypt the data and write it into the fd given by the real transport.
try (InputStream inputStream = new FileInputStream(encryptedStorageFile)) {
mKeyValueEncrypter.decryptKeyValueData(nextRestorePackage, inputStream, outFd);
encryptedStorageFile.delete();
} catch (Exception e) {
Log.e(TAG, "Failed to decrypt restored data: ", e);
return BackupTransport.TRANSPORT_ERROR;
}
return BackupTransport.TRANSPORT_OK;
}
@Override
public RestoreDescription nextRestorePackage() throws RemoteException {
if (!mShouldEncrypt) {
return super.nextRestorePackage();
}
RestoreDescription restoreDescription = super.nextRestorePackage();
mNextRestorePackage.set(restoreDescription.getPackageName());
return restoreDescription;
}
@VisibleForTesting
protected File getBackupTempStorage(String packageName) {
return getTempStorage(packageName, BACKUP_TEMP_DIR);
}
@VisibleForTesting
protected File getRestoreTempStorage(String packageName) {
return getTempStorage(packageName, RESTORE_TEMP_DIR);
}
private File getTempStorage(String packageName, String operationType) {
File encryptedDir = new File(mContext.getFilesDir(), operationType);
encryptedDir.mkdir();
File encryptedFile = new File(encryptedDir, packageName);
try {
encryptedFile.createNewFile();
} catch (IOException e) {
Log.e(TAG, "Failed to create temp file for encrypted data: ", e);
}
return encryptedFile;
}
private void connect() throws RemoteException {
Log.i(TAG, "connecting " + mTransportClient);
synchronized (mConnectLock) {
@@ -65,4 +203,9 @@ public class IntermediateEncryptingTransport extends DelegatingTransport {
TransportClient getClient() {
return mTransportClient;
}
@VisibleForTesting
void setNextRestorePackage(String nextRestorePackage) {
mNextRestorePackage.set(nextRestorePackage);
}
}

View File

@@ -26,20 +26,20 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.backup.transport.TransportClientManager;
import com.android.server.backup.transport.TransportStats;
import java.util.HashMap;
import java.util.Map;
/**
* Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances.
*/
/** Handles creation and cleanup of {@link IntermediateEncryptingTransport} instances. */
public class IntermediateEncryptingTransportManager {
private static final String CALLER = "IntermediateEncryptingTransportManager";
private final TransportClientManager mTransportClientManager;
private final Object mTransportsLock = new Object();
private final Map<ComponentName, IntermediateEncryptingTransport> mTransports = new HashMap<>();
private Context mContext;
@VisibleForTesting
IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) {
@@ -48,6 +48,7 @@ public class IntermediateEncryptingTransportManager {
public IntermediateEncryptingTransportManager(Context context) {
this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
mContext = context;
}
/**
@@ -55,31 +56,42 @@ public class IntermediateEncryptingTransportManager {
* provide a {@link IntermediateEncryptingTransport} which is an implementation of {@link
* IBackupTransport} that encrypts (or decrypts) the data when sending it (or receiving it) from
* the real {@link IBackupTransport}.
*
* @param intent {@link Intent} created with a call to {@link
* TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
* TransportClientManager.getEncryptingTransportIntent(ComponentName)}.
* @return
*/
public IntermediateEncryptingTransport get(Intent intent) {
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);
Log.i(TAG, "get: intent:" + intent + " transportIntent:" + transportIntent);
synchronized (mTransportsLock) {
return mTransports.computeIfAbsent(transportIntent.getComponent(),
c -> create(transportIntent));
return mTransports.computeIfAbsent(
transportIntent.getComponent(), c -> create(transportIntent));
}
}
/**
* Create an instance of {@link IntermediateEncryptingTransport}.
*/
/** Create an instance of {@link IntermediateEncryptingTransport}. */
private IntermediateEncryptingTransport create(Intent realTransportIntent) {
Log.d(TAG, "create: intent:" + realTransportIntent);
return new IntermediateEncryptingTransport(mTransportClientManager.getTransportClient(
realTransportIntent.getComponent(), realTransportIntent.getExtras(), CALLER));
LockPatternUtils patternUtils = new LockPatternUtils(mContext);
boolean shouldEncrypt =
realTransportIntent.getComponent().getClassName().contains("EncryptedLocalTransport")
&& (patternUtils.isLockPatternEnabled(UserHandle.myUserId())
|| patternUtils.isLockPasswordEnabled(UserHandle.myUserId()));
return new IntermediateEncryptingTransport(
mTransportClientManager.getTransportClient(
realTransportIntent.getComponent(),
realTransportIntent.getExtras(),
CALLER),
mContext,
shouldEncrypt);
}
/**
* Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to
* {@link #get(Intent)} with this {@link Intent}.
* Cleanup the {@link IntermediateEncryptingTransport} which was created by a call to {@link
* #get(Intent)} with this {@link Intent}.
*/
public void cleanup(Intent intent) {
Intent transportIntent = TransportClientManager.getRealTransportIntent(intent);

View File

@@ -18,43 +18,71 @@ package com.android.server.backup.encryption.transport;
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.backup.BackupTransport;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.ParcelFileDescriptor;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.encryption.KeyValueEncrypter;
import com.android.server.backup.transport.TransportClient;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class IntermediateEncryptingTransportTest {
@Mock private IBackupTransport mRealTransport;
@Mock private TransportClient mTransportClient;
private static final String TEST_PACKAGE_NAME = "test_package";
private IntermediateEncryptingTransport mIntermediateEncryptingTransport;
private final PackageInfo mTestPackage = new PackageInfo();
@Mock private IBackupTransport mRealTransport;
@Mock private TransportClient mTransportClient;
@Mock private ParcelFileDescriptor mParcelFileDescriptor;
@Mock private KeyValueEncrypter mKeyValueEncrypter;
@Mock private Context mContext;
@Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
private File mTempFile;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mIntermediateEncryptingTransport = new IntermediateEncryptingTransport(mTransportClient);
mIntermediateEncryptingTransport =
new IntermediateEncryptingTransport(
mTransportClient, mContext, mKeyValueEncrypter, true);
mTestPackage.packageName = TEST_PACKAGE_NAME;
mTempFile = mTemporaryFolder.newFile();
when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
when(mRealTransport.getRestoreData(any())).thenReturn(BackupTransport.TRANSPORT_OK);
}
@Test
public void testGetDelegate_callsConnect() throws Exception {
when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
assertEquals(mRealTransport, ret);
@@ -74,4 +102,79 @@ public class IntermediateEncryptingTransportTest {
verify(mTransportClient, times(1)).connect(anyString());
verifyNoMoreInteractions(mTransportClient);
}
@Test
public void testPerformBackup_shouldEncryptTrue_encryptsDataAndPassesToDelegate()
throws Exception {
mIntermediateEncryptingTransport =
new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
verify(mKeyValueEncrypter, times(1))
.encryptKeyValueData(eq(TEST_PACKAGE_NAME), eq(mParcelFileDescriptor), any());
verify(mRealTransport, times(1)).performBackup(eq(mTestPackage), any(), eq(0));
}
@Test
public void testPerformBackup_shouldEncryptFalse_doesntEncryptDataAndPassedToDelegate()
throws Exception {
mIntermediateEncryptingTransport =
new TestIntermediateTransport(
mTransportClient, mContext, mKeyValueEncrypter, false);
mIntermediateEncryptingTransport.performBackup(mTestPackage, mParcelFileDescriptor, 0);
verifyZeroInteractions(mKeyValueEncrypter);
verify(mRealTransport, times(1))
.performBackup(eq(mTestPackage), eq(mParcelFileDescriptor), eq(0));
}
@Test
public void testGetRestoreData_shouldEncryptTrue_decryptsDataAndPassesToDelegate()
throws Exception {
mIntermediateEncryptingTransport =
new TestIntermediateTransport(mTransportClient, mContext, mKeyValueEncrypter, true);
mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
verify(mKeyValueEncrypter, times(1))
.decryptKeyValueData(eq(TEST_PACKAGE_NAME), any(), eq(mParcelFileDescriptor));
verify(mRealTransport, times(1)).getRestoreData(any());
}
@Test
public void testGetRestoreData_shouldEncryptFalse_doesntDecryptDataAndPassesToDelegate()
throws Exception {
mIntermediateEncryptingTransport =
new TestIntermediateTransport(
mTransportClient, mContext, mKeyValueEncrypter, false);
mIntermediateEncryptingTransport.setNextRestorePackage(TEST_PACKAGE_NAME);
mIntermediateEncryptingTransport.getRestoreData(mParcelFileDescriptor);
verifyZeroInteractions(mKeyValueEncrypter);
verify(mRealTransport, times(1)).getRestoreData(eq(mParcelFileDescriptor));
}
private final class TestIntermediateTransport extends IntermediateEncryptingTransport {
TestIntermediateTransport(
TransportClient transportClient,
Context context,
KeyValueEncrypter keyValueEncrypter,
boolean shouldEncrypt) {
super(transportClient, context, keyValueEncrypter, shouldEncrypt);
}
@Override
protected File getBackupTempStorage(String packageName) {
return mTempFile;
}
@Override
protected File getRestoreTempStorage(String packageName) {
return mTempFile;
}
}
}

View File

@@ -0,0 +1,27 @@
//
// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
android_app {
name: "EncryptedLocalTransport",
srcs: ["src/**/*.java"],
optimize: {
proguard_flags_files: ["proguard.flags"],
},
static_libs: ["LocalTransport"],
platform_apis: true,
certificate: "platform",
privileged: true,
}

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/*
* Copyright (c) 2019 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.encryptedlocaltransport"
android:sharedUserId="android.uid.system" >
<application android:allowBackup="false" >
<!-- This service does not need to be exported because it shares uid with the system server
which is the only client. -->
<service android:name=".EncryptedLocalTransportService"
android:permission="android.permission.CONFIRM_FULL_BACKUP"
android:exported="false">
<intent-filter>
<action android:name="android.backup.TRANSPORT_HOST" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -0,0 +1,2 @@
-keep class com.android.localTransport.EncryptedLocalTransport
-keep class com.android.localTransport.EncryptedLocalTransportService

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.encryptedlocaltransport;
import android.app.backup.BackupTransport;
import android.app.backup.RestoreDescription;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;
import android.util.Log;
import com.android.localtransport.LocalTransport;
import com.android.localtransport.LocalTransportParameters;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
public class EncryptedLocalTransport extends LocalTransport {
private static final String TAG = "EncryptedLocalTransport";
private static final int BACKUP_BUFFER_SIZE = 32 * 1024; // 32 KB.
public EncryptedLocalTransport(Context context,
LocalTransportParameters parameters) {
super(context, parameters);
}
@Override
public int performBackup(
PackageInfo packageInfo, ParcelFileDescriptor data, int flags) {
File packageFile;
try {
StructStat stat = Os.fstat(data.getFileDescriptor());
if (stat.st_size > KEY_VALUE_BACKUP_SIZE_QUOTA) {
Log.w(TAG, "New datastore size " + stat.st_size
+ " exceeds quota " + KEY_VALUE_BACKUP_SIZE_QUOTA);
return TRANSPORT_QUOTA_EXCEEDED;
}
} catch (ErrnoException e) {
Log.w(TAG, "Failed to stat the backup input file: ", e);
return BackupTransport.TRANSPORT_ERROR;
}
clearBackupData(packageInfo);
try (InputStream in = new FileInputStream(data.getFileDescriptor())) {
packageFile = new File(mCurrentSetIncrementalDir, packageInfo.packageName);
Files.copy(in, packageFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
Log.w(TAG, "Failed to save backup data to file: ", e);
return BackupTransport.TRANSPORT_ERROR;
}
return TRANSPORT_OK;
}
@Override
public int getRestoreData(ParcelFileDescriptor outFd) {
if (mRestorePackages == null) {
throw new IllegalStateException("startRestore not called");
}
if (mRestorePackage < 0) {
throw new IllegalStateException("nextRestorePackage not called");
}
if (mRestoreType != RestoreDescription.TYPE_KEY_VALUE) {
throw new IllegalStateException("getRestoreData(fd) for non-key/value dataset");
}
try(OutputStream out = new FileOutputStream(outFd.getFileDescriptor())) {
File packageFile = new File(mRestoreSetIncrementalDir,
mRestorePackages[mRestorePackage].packageName);
Files.copy(packageFile.toPath(), out);
} catch (IOException e) {
Log.d(TAG, "Failed to transfer restore data: " + e);
return BackupTransport.TRANSPORT_ERROR;
}
return BackupTransport.TRANSPORT_OK;
}
@Override
protected boolean hasRestoreDataForPackage(String packageName) {
File contents = (new File(mRestoreSetIncrementalDir, packageName));
return contents.exists() && contents.length() != 0;
}
}

View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.encryptedlocaltransport;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import com.android.localtransport.LocalTransportParameters;
public class EncryptedLocalTransportService extends Service {
private static EncryptedLocalTransport sTransport = null;
@Override
public void onCreate() {
if (sTransport == null) {
LocalTransportParameters parameters =
new LocalTransportParameters(getMainThreadHandler(), getContentResolver());
sTransport = new EncryptedLocalTransport(this, parameters);
}
sTransport.getParameters().start();
}
@Override
public void onDestroy() {
sTransport.getParameters().stop();
}
@Override
public IBinder onBind(Intent intent) {
return sTransport.getBinder();
}
}

View File

@@ -71,19 +71,19 @@ public class LocalTransport extends BackupTransport {
// Size quotas at reasonable values, similar to the current cloud-storage limits
private static final long FULL_BACKUP_SIZE_QUOTA = 25 * 1024 * 1024;
private static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
protected static final long KEY_VALUE_BACKUP_SIZE_QUOTA = 5 * 1024 * 1024;
private Context mContext;
private File mDataDir;
private File mCurrentSetDir;
private File mCurrentSetIncrementalDir;
protected File mCurrentSetIncrementalDir;
private File mCurrentSetFullDir;
private PackageInfo[] mRestorePackages = null;
private int mRestorePackage = -1; // Index into mRestorePackages
private int mRestoreType;
protected PackageInfo[] mRestorePackages = null;
protected int mRestorePackage = -1; // Index into mRestorePackages
protected int mRestoreType;
private File mRestoreSetDir;
private File mRestoreSetIncrementalDir;
protected File mRestoreSetIncrementalDir;
private File mRestoreSetFullDir;
// Additional bookkeeping for full backup
@@ -115,7 +115,7 @@ public class LocalTransport extends BackupTransport {
makeDataDirs();
}
LocalTransportParameters getParameters() {
public LocalTransportParameters getParameters() {
return mParameters;
}
@@ -537,14 +537,14 @@ public class LocalTransport extends BackupTransport {
int bytesLeft = numBytes;
while (bytesLeft > 0) {
try {
int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
if (nRead < 0) {
// Something went wrong if we expect data but saw EOD
Log.w(TAG, "Unexpected EOD; failing backup");
return TRANSPORT_ERROR;
}
mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
bytesLeft -= nRead;
int nRead = mSocketInputStream.read(mFullBackupBuffer, 0, bytesLeft);
if (nRead < 0) {
// Something went wrong if we expect data but saw EOD
Log.w(TAG, "Unexpected EOD; failing backup");
return TRANSPORT_ERROR;
}
mFullBackupOutputStream.write(mFullBackupBuffer, 0, nRead);
bytesLeft -= nRead;
} catch (IOException e) {
Log.e(TAG, "Error handling backup data for " + mFullTargetPackage);
return TRANSPORT_ERROR;
@@ -620,20 +620,15 @@ public class LocalTransport extends BackupTransport {
}
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");
boolean found = false;
boolean found;
while (++mRestorePackage < mRestorePackages.length) {
String name = mRestorePackages[mRestorePackage].packageName;
// If we have key/value data for this package, deliver that
// skip packages where we have a data dir but no actual contents
String[] contents = (new File(mRestoreSetIncrementalDir, name)).list();
if (contents != null && contents.length > 0) {
if (DEBUG) {
Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ "
+ mRestorePackage + " = " + name);
}
found = hasRestoreDataForPackage(name);
if (found) {
mRestoreType = RestoreDescription.TYPE_KEY_VALUE;
found = true;
}
if (!found) {
@@ -664,6 +659,18 @@ public class LocalTransport extends BackupTransport {
return RestoreDescription.NO_MORE_PACKAGES;
}
protected boolean hasRestoreDataForPackage(String packageName) {
String[] contents = (new File(mRestoreSetIncrementalDir, packageName)).list();
if (contents != null && contents.length > 0) {
if (DEBUG) {
Log.v(TAG, " nextRestorePackage(TYPE_KEY_VALUE) @ "
+ mRestorePackage + " = " + packageName);
}
return true;
}
return false;
}
@Override
public int getRestoreData(ParcelFileDescriptor outFd) {
if (mRestorePackages == null) throw new IllegalStateException("startRestore not called");

View File

@@ -22,7 +22,7 @@ import android.os.Handler;
import android.provider.Settings;
import android.util.KeyValueListParser;
class LocalTransportParameters extends KeyValueSettingObserver {
public class LocalTransportParameters extends KeyValueSettingObserver {
private static final String TAG = "LocalTransportParams";
private static final String SETTING = Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS;
private static final String KEY_FAKE_ENCRYPTION_FLAG = "fake_encryption_flag";
@@ -31,7 +31,7 @@ class LocalTransportParameters extends KeyValueSettingObserver {
private boolean mFakeEncryptionFlag;
private boolean mIsNonIncrementalOnly;
LocalTransportParameters(Handler handler, ContentResolver resolver) {
public LocalTransportParameters(Handler handler, ContentResolver resolver) {
super(handler, resolver, Settings.Secure.getUriFor(SETTING));
}

View File

@@ -239,7 +239,6 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
private final KeyValueBackupReporter mReporter;
private final OnTaskFinishedListener mTaskFinishedListener;
private final boolean mUserInitiated;
private final boolean mNonIncremental;
private final int mCurrentOpToken;
private final int mUserId;
private final File mStateDirectory;
@@ -264,6 +263,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
// and at least one of the packages had data. Used to avoid updating current token for
// empty backups.
private boolean mHasDataToBackup;
private boolean mNonIncremental;
/**
* This {@link ConditionVariable} is used to signal that the cancel operation has been
@@ -412,6 +412,11 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable {
try {
IBackupTransport transport = mTransportClient.connectOrThrow("KVBT.startTask()");
String transportName = transport.name();
if (transportName.contains("EncryptedLocalTransport")) {
// Temporary code for EiTF POC. Only supports non-incremental backups.
mNonIncremental = true;
}
mReporter.onTransportReady(transportName);
// If we haven't stored PM metadata yet, we must initialize the transport.