summaryrefslogtreecommitdiff
path: root/packages/BackupEncryption/src
diff options
context:
space:
mode:
authorRuslan Tkhakokhov <rthakohov@google.com>2019-09-30 17:36:12 +0100
committerRuslan Tkhakokhov <rthakohov@google.com>2019-10-02 12:57:50 +0100
commitf45db5e35ac14a697af3f1d1419e36bc711e21aa (patch)
tree6010d4ead40f43d65d63df75f24c519504517bb2 /packages/BackupEncryption/src
parentc087fbc6f4837ddf0d2d69551bb89277eef150ae (diff)
Import EncryptedKvBackupTask
Bug: 111386661 Test: atest EncryptedKvBackupTaskTest Change-Id: Id9cd0a57c3ed33f18d68e7fad60035e8a956a1de
Diffstat (limited to 'packages/BackupEncryption/src')
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTask.java244
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NonIncrementalBackupRequiredException.java25
2 files changed, 269 insertions, 0 deletions
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTask.java
new file mode 100644
index 000000000000..619438c7f6fe
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTask.java
@@ -0,0 +1,244 @@
+/*
+ * 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.tasks;
+
+import android.annotation.Nullable;
+import android.app.backup.BackupDataInput;
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.LockScreenRequiredException;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.backup.encryption.CryptoSettings;
+import com.android.server.backup.encryption.chunking.ProtoStore;
+import com.android.server.backup.encryption.client.CryptoBackupServer;
+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 com.android.server.backup.encryption.protos.nano.ChunksMetadataProto;
+import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
+import java.security.SecureRandom;
+import java.security.UnrecoverableKeyException;
+import java.util.Optional;
+
+// TODO(b/141975695): Create a base class for EncryptedKvBackupTask and EncryptedFullBackupTask.
+/** Performs encrypted key value backup, handling rotating the tertiary key as necessary. */
+public class EncryptedKvBackupTask {
+ private static final String TAG = "EncryptedKvBackupTask";
+
+ private final TertiaryKeyManager mTertiaryKeyManager;
+ private final RecoverableKeyStoreSecondaryKey mSecondaryKey;
+ private final ProtoStore<KeyValueListingProto.KeyValueListing> mKeyValueListingStore;
+ private final ProtoStore<ChunksMetadataProto.ChunkListing> mChunkListingStore;
+ private final KvBackupEncrypter mKvBackupEncrypter;
+ private final EncryptedBackupTask mEncryptedBackupTask;
+ private final String mPackageName;
+
+ /** Constructs new instances of {@link EncryptedKvBackupTask}. */
+ public static class EncryptedKvBackupTaskFactory {
+ /**
+ * Creates a new instance.
+ *
+ * <p>Either initializes encrypted backup or loads an existing secondary key as necessary.
+ *
+ * @param cryptoSettings to load secondary key state from
+ * @param fileDescriptor to read the backup data from
+ */
+ public EncryptedKvBackupTask newInstance(
+ Context context,
+ SecureRandom secureRandom,
+ CryptoBackupServer cryptoBackupServer,
+ CryptoSettings cryptoSettings,
+ RecoverableKeyStoreSecondaryKeyManager
+ .RecoverableKeyStoreSecondaryKeyManagerProvider
+ recoverableSecondaryKeyManagerProvider,
+ ParcelFileDescriptor fileDescriptor,
+ String packageName)
+ throws IOException, UnrecoverableKeyException, LockScreenRequiredException,
+ InternalRecoveryServiceException, InvalidKeyException {
+ RecoverableKeyStoreSecondaryKey secondaryKey =
+ new InitializeRecoverableSecondaryKeyTask(
+ context,
+ cryptoSettings,
+ recoverableSecondaryKeyManagerProvider.get(),
+ cryptoBackupServer)
+ .run();
+ KvBackupEncrypter backupEncrypter =
+ new KvBackupEncrypter(new BackupDataInput(fileDescriptor.getFileDescriptor()));
+ TertiaryKeyManager tertiaryKeyManager =
+ new TertiaryKeyManager(
+ context,
+ secureRandom,
+ TertiaryKeyRotationScheduler.getInstance(context),
+ secondaryKey,
+ packageName);
+
+ return new EncryptedKvBackupTask(
+ tertiaryKeyManager,
+ ProtoStore.createKeyValueListingStore(context),
+ secondaryKey,
+ ProtoStore.createChunkListingStore(context),
+ backupEncrypter,
+ new EncryptedBackupTask(
+ cryptoBackupServer, secureRandom, packageName, backupEncrypter),
+ packageName);
+ }
+ }
+
+ @VisibleForTesting
+ EncryptedKvBackupTask(
+ TertiaryKeyManager tertiaryKeyManager,
+ ProtoStore<KeyValueListingProto.KeyValueListing> keyValueListingStore,
+ RecoverableKeyStoreSecondaryKey secondaryKey,
+ ProtoStore<ChunksMetadataProto.ChunkListing> chunkListingStore,
+ KvBackupEncrypter kvBackupEncrypter,
+ EncryptedBackupTask encryptedBackupTask,
+ String packageName) {
+ mTertiaryKeyManager = tertiaryKeyManager;
+ mSecondaryKey = secondaryKey;
+ mKeyValueListingStore = keyValueListingStore;
+ mChunkListingStore = chunkListingStore;
+ mKvBackupEncrypter = kvBackupEncrypter;
+ mEncryptedBackupTask = encryptedBackupTask;
+ mPackageName = packageName;
+ }
+
+ /**
+ * Reads backup data from the file descriptor provided in the construtor, encrypts it and
+ * uploads it to the server.
+ *
+ * <p>The {@code incremental} flag indicates if the backup data provided is incremental or a
+ * complete set. Incremental backup is not possible if no previous crypto state exists, or the
+ * tertiary key must be rotated in the next backup. If the caller requests incremental backup
+ * but it is not possible, then the backup will not start and this method will throw {@link
+ * NonIncrementalBackupRequiredException}.
+ *
+ * <p>TODO(b/70704456): Update return code to indicate that we require non-incremental backup.
+ *
+ * @param incremental {@code true} if the data provided is a diff from the previous backup,
+ * {@code false} if it is a complete set
+ * @throws NonIncrementalBackupRequiredException if the caller provides an incremental backup but the task
+ * requires non-incremental backup
+ */
+ public void performBackup(boolean incremental)
+ throws GeneralSecurityException, IOException, NoSuchMethodException,
+ InstantiationException, IllegalAccessException, InvocationTargetException,
+ NonIncrementalBackupRequiredException {
+ if (mTertiaryKeyManager.wasKeyRotated()) {
+ Slog.d(TAG, "Tertiary key is new so clearing package state.");
+ deleteListings(mPackageName);
+ }
+
+ Optional<Pair<KeyValueListingProto.KeyValueListing, ChunksMetadataProto.ChunkListing>>
+ oldListings = getListingsAndEnsureConsistency(mPackageName);
+
+ if (oldListings.isPresent() && !incremental) {
+ Slog.d(
+ TAG,
+ "Non-incremental backup requested but incremental state existed, clearing it");
+ deleteListings(mPackageName);
+ oldListings = Optional.empty();
+ }
+
+ if (!oldListings.isPresent() && incremental) {
+ // If we don't have any state then we require a non-incremental backup, but this backup
+ // is incremental.
+ throw new NonIncrementalBackupRequiredException();
+ }
+
+ if (oldListings.isPresent()) {
+ mKvBackupEncrypter.setOldKeyValueListing(oldListings.get().first);
+ }
+
+ ChunksMetadataProto.ChunkListing newChunkListing;
+ if (oldListings.isPresent()) {
+ Slog.v(TAG, "Old listings existed, performing incremental backup");
+ newChunkListing =
+ mEncryptedBackupTask.performIncrementalBackup(
+ mTertiaryKeyManager.getKey(),
+ mTertiaryKeyManager.getWrappedKey(),
+ oldListings.get().second);
+ } else {
+ Slog.v(TAG, "Old listings did not exist, performing non-incremental backup");
+ // kv backups don't use this salt because they don't involve content-defined chunking.
+ byte[] fingerprintMixerSalt = null;
+ newChunkListing =
+ mEncryptedBackupTask.performNonIncrementalBackup(
+ mTertiaryKeyManager.getKey(),
+ mTertiaryKeyManager.getWrappedKey(),
+ fingerprintMixerSalt);
+ }
+
+ Slog.v(TAG, "Backup and upload succeeded, saving new listings");
+ saveListings(mPackageName, mKvBackupEncrypter.getNewKeyValueListing(), newChunkListing);
+ }
+
+ private Optional<Pair<KeyValueListingProto.KeyValueListing, ChunksMetadataProto.ChunkListing>>
+ getListingsAndEnsureConsistency(String packageName)
+ throws IOException, InvocationTargetException, NoSuchMethodException,
+ InstantiationException, IllegalAccessException {
+ Optional<KeyValueListingProto.KeyValueListing> keyValueListing =
+ mKeyValueListingStore.loadProto(packageName);
+ Optional<ChunksMetadataProto.ChunkListing> chunkListing =
+ mChunkListingStore.loadProto(packageName);
+
+ // Normally either both protos exist or neither exist, but we correct this just in case.
+ boolean bothPresent = keyValueListing.isPresent() && chunkListing.isPresent();
+ if (!bothPresent) {
+ Slog.d(
+ TAG,
+ "Both listing were not present, clearing state, key value="
+ + keyValueListing.isPresent()
+ + ", chunk="
+ + chunkListing.isPresent());
+ deleteListings(packageName);
+ return Optional.empty();
+ }
+
+ return Optional.of(Pair.create(keyValueListing.get(), chunkListing.get()));
+ }
+
+ private void saveListings(
+ String packageName,
+ KeyValueListingProto.KeyValueListing keyValueListing,
+ ChunksMetadataProto.ChunkListing chunkListing) {
+ try {
+ mKeyValueListingStore.saveProto(packageName, keyValueListing);
+ mChunkListingStore.saveProto(packageName, chunkListing);
+ } catch (IOException e) {
+ // If a problem occurred while saving either listing then they may be inconsistent, so
+ // delete
+ // both.
+ Slog.w(TAG, "Unable to save listings, deleting both for consistency", e);
+ deleteListings(packageName);
+ }
+ }
+
+ private void deleteListings(String packageName) {
+ mKeyValueListingStore.deleteProto(packageName);
+ mChunkListingStore.deleteProto(packageName);
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NonIncrementalBackupRequiredException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NonIncrementalBackupRequiredException.java
new file mode 100644
index 000000000000..a3eda7d1270f
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NonIncrementalBackupRequiredException.java
@@ -0,0 +1,25 @@
+/*
+ * 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.tasks;
+
+// TODO(141840878): Update documentation.
+/**
+ * Exception thrown when the framework provides an incremental backup but the transport requires a
+ * non-incremental backup.
+ */
+public class NonIncrementalBackupRequiredException extends Exception {}