summaryrefslogtreecommitdiff
path: root/packages/BackupEncryption/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/BackupEncryption/src')
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/client/UnexpectedActiveSecondaryOnServerException.java30
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ActiveSecondaryNotInKeychainException.java27
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NoActiveSecondaryKeyException.java28
-rw-r--r--packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java270
4 files changed, 355 insertions, 0 deletions
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/client/UnexpectedActiveSecondaryOnServerException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/client/UnexpectedActiveSecondaryOnServerException.java
new file mode 100644
index 000000000000..9e31385c9525
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/client/UnexpectedActiveSecondaryOnServerException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.client;
+
+/**
+ * Error thrown when the user attempts to retrieve a key set from the server, but is asking for keys
+ * from an inactive secondary.
+ *
+ * <p>Although we could just return old keys, there is no good reason to do this. It almost
+ * certainly indicates a logic error on the client.
+ */
+public class UnexpectedActiveSecondaryOnServerException extends Exception {
+ public UnexpectedActiveSecondaryOnServerException(String message) {
+ super(message);
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ActiveSecondaryNotInKeychainException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ActiveSecondaryNotInKeychainException.java
new file mode 100644
index 000000000000..2e8a61f05970
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ActiveSecondaryNotInKeychainException.java
@@ -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.
+ */
+
+package com.android.server.backup.encryption.tasks;
+
+/**
+ * Error thrown when the server's active secondary key does not exist in the user's recoverable
+ * keychain. This means the backup data cannot be decrypted, and should be wiped.
+ */
+public class ActiveSecondaryNotInKeychainException extends Exception {
+ public ActiveSecondaryNotInKeychainException(String message) {
+ super(message);
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NoActiveSecondaryKeyException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NoActiveSecondaryKeyException.java
new file mode 100644
index 000000000000..72e8a89f1df3
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NoActiveSecondaryKeyException.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Error thrown if attempting to rotate key when there is no current active secondary key set
+ * locally. This means the device needs to re-initialize, asking the backup server what the active
+ * secondary key is.
+ */
+public class NoActiveSecondaryKeyException extends Exception {
+ public NoActiveSecondaryKeyException(String message) {
+ super(message);
+ }
+}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java
new file mode 100644
index 000000000000..d58cb66ef6b4
--- /dev/null
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java
@@ -0,0 +1,270 @@
+/*
+ * 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 static android.os.Build.VERSION_CODES.P;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.content.Context;
+import android.security.keystore.recovery.InternalRecoveryServiceException;
+import android.security.keystore.recovery.RecoveryController;
+import android.util.Slog;
+
+import com.android.server.backup.encryption.CryptoSettings;
+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.keys.RecoverableKeyStoreSecondaryKeyManager;
+import com.android.server.backup.encryption.keys.TertiaryKeyStore;
+import com.android.server.backup.encryption.protos.nano.WrappedKeyProto;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+/**
+ * Finishes a rotation for a {@link
+ * com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey}.
+ */
+public class RotateSecondaryKeyTask {
+ private static final String TAG = "RotateSecondaryKeyTask";
+
+ private final Context mContext;
+ private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager;
+ private final CryptoBackupServer mBackupServer;
+ private final CryptoSettings mCryptoSettings;
+ private final RecoveryController mRecoveryController;
+
+ /**
+ * A new instance.
+ *
+ * @param secondaryKeyManager For loading the currently active and next secondary key.
+ * @param backupServer For loading and storing tertiary keys and for setting active secondary
+ * key.
+ * @param cryptoSettings For checking the stored aliases for the next and active key.
+ * @param recoveryController For communicating with the Framework apis.
+ */
+ public RotateSecondaryKeyTask(
+ Context context,
+ RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager,
+ CryptoBackupServer backupServer,
+ CryptoSettings cryptoSettings,
+ RecoveryController recoveryController) {
+ mContext = context;
+ mSecondaryKeyManager = checkNotNull(secondaryKeyManager);
+ mCryptoSettings = checkNotNull(cryptoSettings);
+ mBackupServer = checkNotNull(backupServer);
+ mRecoveryController = checkNotNull(recoveryController);
+ }
+
+ /** Runs the task. */
+ public void run() {
+ // Never run more than one of these at the same time.
+ synchronized (RotateSecondaryKeyTask.class) {
+ runInternal();
+ }
+ }
+
+ private void runInternal() {
+ Optional<RecoverableKeyStoreSecondaryKey> maybeNextKey;
+ try {
+ maybeNextKey = getNextKey();
+ } catch (Exception e) {
+ Slog.e(TAG, "Error checking for next key", e);
+ return;
+ }
+
+ if (!maybeNextKey.isPresent()) {
+ Slog.d(TAG, "No secondary key rotation task pending. Exiting.");
+ return;
+ }
+
+ RecoverableKeyStoreSecondaryKey nextKey = maybeNextKey.get();
+ boolean isReady;
+ try {
+ isReady = isSecondaryKeyRotationReady(nextKey);
+ } catch (InternalRecoveryServiceException e) {
+ Slog.e(TAG, "Error encountered checking whether next secondary key is synced", e);
+ return;
+ }
+
+ if (!isReady) {
+ return;
+ }
+
+ try {
+ rotateToKey(nextKey);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error trying to rotate to new secondary key", e);
+ }
+ }
+
+ private Optional<RecoverableKeyStoreSecondaryKey> getNextKey()
+ throws InternalRecoveryServiceException, UnrecoverableKeyException {
+ Optional<String> maybeNextAlias = mCryptoSettings.getNextSecondaryKeyAlias();
+ if (!maybeNextAlias.isPresent()) {
+ return Optional.empty();
+ }
+ return mSecondaryKeyManager.get(maybeNextAlias.get());
+ }
+
+ private boolean isSecondaryKeyRotationReady(RecoverableKeyStoreSecondaryKey nextKey)
+ throws InternalRecoveryServiceException {
+ String nextAlias = nextKey.getAlias();
+ Slog.i(TAG, "Key rotation to " + nextAlias + " is pending. Checking key sync status.");
+ int status = mRecoveryController.getRecoveryStatus(nextAlias);
+
+ if (status == RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE) {
+ Slog.e(
+ TAG,
+ "Permanent failure to sync " + nextAlias + ". Cannot possibly rotate to it.");
+ mCryptoSettings.removeNextSecondaryKeyAlias();
+ return false;
+ }
+
+ if (status == RecoveryController.RECOVERY_STATUS_SYNCED) {
+ Slog.i(TAG, "Secondary key " + nextAlias + " has now synced! Commencing rotation.");
+ } else {
+ Slog.i(TAG, "Sync still pending for " + nextAlias);
+ }
+ return status == RecoveryController.RECOVERY_STATUS_SYNCED;
+ }
+
+ /**
+ * @throws ActiveSecondaryNotInKeychainException if the currently active secondary key is not in
+ * the keychain.
+ * @throws IOException if there is an IO issue communicating with the server or loading from
+ * disk.
+ * @throws NoActiveSecondaryKeyException if there is no active key set.
+ * @throws IllegalBlockSizeException if there is an issue decrypting a tertiary key.
+ * @throws InvalidKeyException if any of the secondary keys cannot be used for wrapping or
+ * unwrapping tertiary keys.
+ */
+ private void rotateToKey(RecoverableKeyStoreSecondaryKey newSecondaryKey)
+ throws ActiveSecondaryNotInKeychainException, IOException,
+ NoActiveSecondaryKeyException, IllegalBlockSizeException, InvalidKeyException,
+ InternalRecoveryServiceException, UnrecoverableKeyException,
+ InvalidAlgorithmParameterException, NoSuchAlgorithmException,
+ NoSuchPaddingException {
+ RecoverableKeyStoreSecondaryKey activeSecondaryKey = getActiveSecondaryKey();
+ String activeSecondaryKeyAlias = activeSecondaryKey.getAlias();
+ String newSecondaryKeyAlias = newSecondaryKey.getAlias();
+ if (newSecondaryKeyAlias.equals(activeSecondaryKeyAlias)) {
+ Slog.i(TAG, activeSecondaryKeyAlias + " was already the active alias.");
+ return;
+ }
+
+ TertiaryKeyStore tertiaryKeyStore =
+ TertiaryKeyStore.newInstance(mContext, activeSecondaryKey);
+ Map<String, SecretKey> tertiaryKeys = tertiaryKeyStore.getAll();
+
+ if (tertiaryKeys.isEmpty()) {
+ Slog.i(
+ TAG,
+ "No tertiary keys for " + activeSecondaryKeyAlias + ". No need to rewrap. ");
+ mBackupServer.setActiveSecondaryKeyAlias(
+ newSecondaryKeyAlias, /*tertiaryKeys=*/ Collections.emptyMap());
+ } else {
+ Map<String, WrappedKeyProto.WrappedKey> rewrappedTertiaryKeys =
+ rewrapAll(newSecondaryKey, tertiaryKeys);
+ TertiaryKeyStore.newInstance(mContext, newSecondaryKey).putAll(rewrappedTertiaryKeys);
+ Slog.i(
+ TAG,
+ "Successfully rewrapped " + rewrappedTertiaryKeys.size() + " tertiary keys");
+ mBackupServer.setActiveSecondaryKeyAlias(newSecondaryKeyAlias, rewrappedTertiaryKeys);
+ Slog.i(
+ TAG,
+ "Successfully uploaded new set of tertiary keys to "
+ + newSecondaryKeyAlias
+ + " alias");
+ }
+
+ mCryptoSettings.setActiveSecondaryKeyAlias(newSecondaryKeyAlias);
+ mCryptoSettings.removeNextSecondaryKeyAlias();
+ try {
+ mRecoveryController.removeKey(activeSecondaryKeyAlias);
+ } catch (InternalRecoveryServiceException e) {
+ Slog.e(TAG, "Error removing old secondary key from RecoverableKeyStoreLoader", e);
+ }
+ }
+
+ private RecoverableKeyStoreSecondaryKey getActiveSecondaryKey()
+ throws NoActiveSecondaryKeyException, ActiveSecondaryNotInKeychainException,
+ InternalRecoveryServiceException, UnrecoverableKeyException {
+
+ Optional<String> activeSecondaryAlias = mCryptoSettings.getActiveSecondaryKeyAlias();
+
+ if (!activeSecondaryAlias.isPresent()) {
+ Slog.i(
+ TAG,
+ "Was asked to rotate secondary key, but local config did not have a secondary "
+ + "key alias set.");
+ throw new NoActiveSecondaryKeyException("No local active secondary key set.");
+ }
+
+ String activeSecondaryKeyAlias = activeSecondaryAlias.get();
+ Optional<RecoverableKeyStoreSecondaryKey> secondaryKey =
+ mSecondaryKeyManager.get(activeSecondaryKeyAlias);
+
+ if (!secondaryKey.isPresent()) {
+ throw new ActiveSecondaryNotInKeychainException(
+ String.format(
+ Locale.US,
+ "Had local active recoverable key alias of %s but key was not in"
+ + " user's keychain.",
+ activeSecondaryKeyAlias));
+ }
+
+ return secondaryKey.get();
+ }
+
+ /**
+ * Rewraps all the tertiary keys.
+ *
+ * @param newSecondaryKey The secondary key with which to rewrap the tertiaries.
+ * @param tertiaryKeys The tertiary keys, by package name.
+ * @return The newly wrapped tertiary keys, by package name.
+ * @throws InvalidKeyException if any key is unusable.
+ * @throws IllegalBlockSizeException if could not decrypt.
+ */
+ private Map<String, WrappedKeyProto.WrappedKey> rewrapAll(
+ RecoverableKeyStoreSecondaryKey newSecondaryKey, Map<String, SecretKey> tertiaryKeys)
+ throws InvalidKeyException, IllegalBlockSizeException, NoSuchPaddingException,
+ NoSuchAlgorithmException {
+ Map<String, WrappedKeyProto.WrappedKey> wrappedKeys = new HashMap<>();
+
+ for (String packageName : tertiaryKeys.keySet()) {
+ SecretKey tertiaryKey = tertiaryKeys.get(packageName);
+ wrappedKeys.put(
+ packageName, KeyWrapUtils.wrap(newSecondaryKey.getSecretKey(), tertiaryKey));
+ }
+
+ return wrappedKeys;
+ }
+}