Merge "Add BackupEncryptionService which provides an IntermediateEncryptingTransport"

This commit is contained in:
Chandan Nath
2019-09-25 16:55:19 +00:00
committed by Android (Google) Code Review
11 changed files with 543 additions and 1 deletions

View File

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

View File

@@ -20,5 +20,14 @@
package="com.android.server.backup.encryption"
android:sharedUserId="android.uid.system" >
<application android:allowBackup="false" />
<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=".BackupEncryptionService"
android:exported="false">
<intent-filter>
<action android:name="android.encryption.BACKUP_ENCRYPTION" />
</intent-filter>
</service>
</application>
</manifest>

View File

@@ -0,0 +1,63 @@
/*
* 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.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.encryption.transport.IntermediateEncryptingTransport;
import com.android.server.backup.encryption.transport.IntermediateEncryptingTransportManager;
/**
* This service provides encryption of backup data. For an intent used to bind to this service, it
* provides an {@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}.
*/
public class BackupEncryptionService extends Service {
public static final String TAG = "BackupEncryption";
private static IntermediateEncryptingTransportManager sTransportManager = null;
@Override
public void onCreate() {
Log.i(TAG, "onCreate:" + this);
if (sTransportManager == null) {
Log.i(TAG, "Creating IntermediateEncryptingTransportManager");
sTransportManager = new IntermediateEncryptingTransportManager(this);
}
}
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy:" + this);
}
@Override
public IBinder onBind(Intent intent) {
// TODO (b141536117): Check connection with TransportClient.connect and return null on fail.
return sTransportManager.get(intent);
}
@Override
public boolean onUnbind(Intent intent) {
sTransportManager.cleanup(intent);
return false;
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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.transport;
import android.os.RemoteException;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.transport.DelegatingTransport;
import com.android.server.backup.transport.TransportClient;
/**
* 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 final TransportClient mTransportClient;
private final Object mConnectLock = new Object();
private volatile IBackupTransport mRealTransport;
@VisibleForTesting
IntermediateEncryptingTransport(TransportClient transportClient) {
mTransportClient = transportClient;
}
@Override
protected IBackupTransport getDelegate() throws RemoteException {
if (mRealTransport == null) {
connect();
}
return mRealTransport;
}
private void connect() throws RemoteException {
synchronized (mConnectLock) {
if (mRealTransport == null) {
mRealTransport = mTransportClient.connect("IntermediateEncryptingTransport");
if (mRealTransport == null) {
throw new RemoteException("Could not connect: " + mTransportClient);
}
}
}
}
@VisibleForTesting
TransportClient getClient() {
return mTransportClient;
}
}

View File

@@ -0,0 +1,97 @@
/*
* 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.transport;
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
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.
*/
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<>();
@VisibleForTesting
IntermediateEncryptingTransportManager(TransportClientManager transportClientManager) {
mTransportClientManager = transportClientManager;
}
public IntermediateEncryptingTransportManager(Context context) {
this(new TransportClientManager(UserHandle.myUserId(), context, new TransportStats()));
}
/**
* Extract the {@link ComponentName} corresponding to the real {@link IBackupTransport}, and
* 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)}.
* @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));
}
}
/**
* Create an instance of {@link IntermediateEncryptingTransport}.
*/
private IntermediateEncryptingTransport create(Intent realTransportIntent) {
return new IntermediateEncryptingTransport(mTransportClientManager.getTransportClient(
realTransportIntent.getComponent(), realTransportIntent.getExtras(), CALLER));
}
/**
* 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);
Log.i(TAG, "cleanup: intent:" + intent + " transportIntent:" + transportIntent);
IntermediateEncryptingTransport transport;
synchronized (mTransportsLock) {
transport = mTransports.remove(transportIntent.getComponent());
}
if (transport != null) {
mTransportClientManager.disposeOfTransportClient(transport.getClient(), CALLER);
} else {
Log.i(TAG, "Could not find IntermediateEncryptingTransport");
}
}
}

View File

@@ -0,0 +1,22 @@
android_test {
name: "BackupEncryptionUnitTests",
srcs: ["src/**/*.java"],
static_libs: [
"androidx.test.runner",
"androidx.test.rules",
"mockito-target-minus-junit4",
"platform-test-annotations",
"truth-prebuilt",
"testables",
"testng",
],
libs: [
"android.test.mock",
"android.test.base",
"android.test.runner",
"BackupEncryption",
],
test_suites: ["device-tests"],
instrumentation_for: "BackupEncryption",
certificate: "platform",
}

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.server.backup.encryption.unittests"
android:sharedUserId="android.uid.system" >
<application android:testOnly="true">
<uses-library android:name="android.test.runner" />
</application>
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.server.backup.encryption"
android:label="Backup Encryption Unit Tests" />
</manifest>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<configuration description="Runs Backup Encryption Unit Tests.">
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="BackupEncryptionUnitTests.apk" />
</target_preparer>
<option name="test-tag" value="BackupEncryptionUnitTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.server.backup.encryption.unittests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
</configuration>

View File

@@ -0,0 +1,120 @@
/*
* 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.transport;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
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.when;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.transport.TransportClientManager;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class IntermediateEncryptingTransportManagerTest {
@Mock private TransportClient mTransportClient;
@Mock private TransportClientManager mTransportClientManager;
private final ComponentName mTransportComponent = new ComponentName("pkg", "class");
private final Bundle mExtras = new Bundle();
private Intent mEncryptingTransportIntent;
private IntermediateEncryptingTransportManager mIntermediateEncryptingTransportManager;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mExtras.putInt("test", 1);
mEncryptingTransportIntent =
TransportClientManager.getEncryptingTransportIntent(mTransportComponent)
.putExtras(mExtras);
mIntermediateEncryptingTransportManager =
new IntermediateEncryptingTransportManager(mTransportClientManager);
}
@Test
public void testGet_createsClientWithRealTransportComponentAndExtras() {
when(mTransportClientManager.getTransportClient(any(), any(), any()))
.thenReturn(mTransportClient);
IntermediateEncryptingTransport intermediateEncryptingTransport =
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
assertEquals(mTransportClient, intermediateEncryptingTransport.getClient());
verify(mTransportClientManager, times(1))
.getTransportClient(eq(mTransportComponent), argThat(mExtras::kindofEquals), any());
verifyNoMoreInteractions(mTransportClientManager);
}
@Test
public void testGet_callTwice_returnsSameTransport() {
IntermediateEncryptingTransport transport1 =
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
IntermediateEncryptingTransport transport2 =
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
assertEquals(transport1, transport2);
}
@Test
public void testCleanup_disposesTransportClient() {
when(mTransportClientManager.getTransportClient(any(), any(), any()))
.thenReturn(mTransportClient);
IntermediateEncryptingTransport transport =
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
verify(mTransportClientManager, times(1)).getTransportClient(any(), any(), any());
verify(mTransportClientManager, times(1))
.disposeOfTransportClient(eq(mTransportClient), any());
verifyNoMoreInteractions(mTransportClientManager);
}
@Test
public void testCleanup_removesCachedTransport() {
when(mTransportClientManager.getTransportClient(any(), any(), any()))
.thenReturn(mTransportClient);
IntermediateEncryptingTransport transport1 =
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
mIntermediateEncryptingTransportManager.cleanup(mEncryptingTransportIntent);
IntermediateEncryptingTransport transport2 =
mIntermediateEncryptingTransportManager.get(mEncryptingTransportIntent);
assertNotSame(transport1, transport2);
}
}

View File

@@ -0,0 +1,77 @@
/*
* 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.transport;
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.transport.TransportClient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class IntermediateEncryptingTransportTest {
@Mock private IBackupTransport mRealTransport;
@Mock private TransportClient mTransportClient;
private IntermediateEncryptingTransport mIntermediateEncryptingTransport;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mIntermediateEncryptingTransport = new IntermediateEncryptingTransport(mTransportClient);
}
@Test
public void testGetDelegate_callsConnect() throws Exception {
when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
IBackupTransport ret = mIntermediateEncryptingTransport.getDelegate();
assertEquals(mRealTransport, ret);
verify(mTransportClient, times(1)).connect(anyString());
verifyNoMoreInteractions(mTransportClient);
}
@Test
public void testGetDelegate_callTwice_callsConnectOnce() throws Exception {
when(mTransportClient.connect(anyString())).thenReturn(mRealTransport);
IBackupTransport ret1 = mIntermediateEncryptingTransport.getDelegate();
IBackupTransport ret2 = mIntermediateEncryptingTransport.getDelegate();
assertEquals(mRealTransport, ret1);
assertEquals(mRealTransport, ret2);
verify(mTransportClient, times(1)).connect(anyString());
verifyNoMoreInteractions(mTransportClient);
}
}