diff options
Diffstat (limited to 'packages/BackupEncryption')
10 files changed, 510 insertions, 1 deletions
diff --git a/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp index 9bcd677538eb..342d796de402 100644 --- a/packages/BackupEncryption/Android.bp +++ b/packages/BackupEncryption/Android.bp @@ -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", diff --git a/packages/BackupEncryption/AndroidManifest.xml b/packages/BackupEncryption/AndroidManifest.xml index a705df5a425b..4d174e3b64d6 100644 --- a/packages/BackupEncryption/AndroidManifest.xml +++ b/packages/BackupEncryption/AndroidManifest.xml @@ -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> diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java new file mode 100644 index 000000000000..84fb0e62dbca --- /dev/null +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/BackupEncryptionService.java @@ -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; + } +} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java new file mode 100644 index 000000000000..da47781d77d1 --- /dev/null +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransport.java @@ -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; + } +} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java new file mode 100644 index 000000000000..5a8b05c9f0fe --- /dev/null +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManager.java @@ -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"); + } + } +} diff --git a/packages/BackupEncryption/test/unittest/Android.bp b/packages/BackupEncryption/test/unittest/Android.bp new file mode 100644 index 000000000000..d7c510b57518 --- /dev/null +++ b/packages/BackupEncryption/test/unittest/Android.bp @@ -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", +}
\ No newline at end of file diff --git a/packages/BackupEncryption/test/unittest/AndroidManifest.xml b/packages/BackupEncryption/test/unittest/AndroidManifest.xml new file mode 100644 index 000000000000..39ac8aa32ebc --- /dev/null +++ b/packages/BackupEncryption/test/unittest/AndroidManifest.xml @@ -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>
\ No newline at end of file diff --git a/packages/BackupEncryption/test/unittest/AndroidTest.xml b/packages/BackupEncryption/test/unittest/AndroidTest.xml new file mode 100644 index 000000000000..c9c812a78194 --- /dev/null +++ b/packages/BackupEncryption/test/unittest/AndroidTest.xml @@ -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> diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java new file mode 100644 index 000000000000..0d43a190cd07 --- /dev/null +++ b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportManagerTest.java @@ -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); + } +} diff --git a/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java new file mode 100644 index 000000000000..cc4b0ab1bb36 --- /dev/null +++ b/packages/BackupEncryption/test/unittest/src/com/android/server/backup/encryption/transport/IntermediateEncryptingTransportTest.java @@ -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); + } +} |