diff options
160 files changed, 0 insertions, 19249 deletions
diff --git a/packages/BackupEncryption/Android.bp b/packages/BackupEncryption/Android.bp deleted file mode 100644 index 0244f289b40c..000000000000 --- a/packages/BackupEncryption/Android.bp +++ /dev/null @@ -1,41 +0,0 @@ -// -// 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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_app { - name: "BackupEncryption", - defaults: ["platform_app_defaults"], - srcs: ["src/**/*.java"], - static_libs: ["backup-encryption-protos", "backuplib"], - optimize: { enabled: false }, - platform_apis: true, - certificate: "platform", - privileged: true, -} - -java_library { - name: "backup-encryption-protos", - proto: { type: "nano" }, - srcs: ["proto/**/*.proto"], -} diff --git a/packages/BackupEncryption/AndroidManifest.xml b/packages/BackupEncryption/AndroidManifest.xml deleted file mode 100644 index 4d174e3b64d6..000000000000 --- a/packages/BackupEncryption/AndroidManifest.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* - * Copyright (c) 2016 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.server.backup.encryption" - 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=".BackupEncryptionService" - android:exported="false"> - <intent-filter> - <action android:name="android.encryption.BACKUP_ENCRYPTION" /> - </intent-filter> - </service> - </application> -</manifest> diff --git a/packages/BackupEncryption/OWNERS b/packages/BackupEncryption/OWNERS deleted file mode 100644 index d99779e3d9da..000000000000 --- a/packages/BackupEncryption/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/backup/OWNERS diff --git a/packages/BackupEncryption/proguard.flags b/packages/BackupEncryption/proguard.flags deleted file mode 100644 index 851ce8caab43..000000000000 --- a/packages/BackupEncryption/proguard.flags +++ /dev/null @@ -1 +0,0 @@ --keep class com.android.server.backup.encryption diff --git a/packages/BackupEncryption/proto/backup_chunks_metadata.proto b/packages/BackupEncryption/proto/backup_chunks_metadata.proto deleted file mode 100644 index 2fdedbf70975..000000000000 --- a/packages/BackupEncryption/proto/backup_chunks_metadata.proto +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2018 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 - */ - -syntax = "proto2"; - -package android_backup_crypto; - -option java_package = "com.android.server.backup.encryption.protos"; -option java_outer_classname = "ChunksMetadataProto"; - -// Cipher type with which the chunks are encrypted. For now we only support AES/GCM/NoPadding, but -// this is for backwards-compatibility in case we need to change the default Cipher in the future. -enum CipherType { - UNKNOWN_CIPHER_TYPE = 0; - // Chunk is prefixed with a 12-byte nonce. The tag length is 16 bytes. - AES_256_GCM = 1; -} - -// Checksum type with which the plaintext is verified. -enum ChecksumType { - UNKNOWN_CHECKSUM_TYPE = 0; - SHA_256 = 1; -} - -enum ChunkOrderingType { - CHUNK_ORDERING_TYPE_UNSPECIFIED = 0; - // The chunk ordering contains a list of the start position of each chunk in the encrypted file, - // ordered as in the plaintext file. This allows us to recreate the original plaintext file - // during decryption. We use this mode for full backups where the order of the data in the file - // is important. - EXPLICIT_STARTS = 1; - // The chunk ordering does not contain any start positions, and instead each encrypted chunk in - // the backup file is prefixed with its length. This allows us to decrypt each chunk but does - // not give any information about the order. However, we use this mode for key value backups - // where the order does not matter. - INLINE_LENGTHS = 2; -} - -// Chunk entry (for local state) -message Chunk { - // SHA-256 MAC of the plaintext of the chunk - optional bytes hash = 1; - // Number of bytes in encrypted chunk - optional int32 length = 2; -} - -// List of the chunks in the blob, along with the length of each chunk. From this is it possible to -// extract individual chunks. (i.e., start position is equal to the sum of the lengths of all -// preceding chunks.) -// -// This is local state stored on the device. It is never sent to the backup server. See -// ChunkOrdering for how the device restores the chunks in the correct order. -// Next tag : 6 -message ChunkListing { - repeated Chunk chunks = 1; - - // Cipher algorithm with which the chunks are encrypted. - optional CipherType cipher_type = 2; - - // Defines the type of chunk order used to encode the backup file on the server, so that we can - // consistently use the same type between backups. If unspecified this backup file was created - // before INLINE_LENGTHS was supported, thus assume it is EXPLICIT_STARTS. - optional ChunkOrderingType chunk_ordering_type = 5; - - // The document ID returned from Scotty server after uploading the blob associated with this - // listing. This needs to be sent when uploading new diff scripts. - optional string document_id = 3; - - // Fingerprint mixer salt used for content defined chunking. This is randomly generated for each - // package during the initial non-incremental backup and reused for incremental backups. - optional bytes fingerprint_mixer_salt = 4; -} - -// Ordering information about plaintext and checksum. This is used on restore to reconstruct the -// blob in its correct order. (The chunk order is randomized so as to give the server less -// information about which parts of the backup are changing over time.) This proto is encrypted -// before being uploaded to the server, with a key unknown to the server. -message ChunkOrdering { - // For backups where ChunksMetadata#chunk_ordering_type = EXPLICIT STARTS: - // Ordered start positions of chunks. i.e., the file is the chunk starting at this position, - // followed by the chunk starting at this position, followed by ... etc. You can compute the - // lengths of the chunks by sorting this list then looking at the start position of the next - // chunk after the chunk you care about. This is guaranteed to work as all chunks are - // represented in this list. - // - // For backups where ChunksMetadata#chunk_ordering_type = INLINE_LENGTHS: - // This field is unused. See ChunkOrderingType#INLINE_LENGTHS. - repeated int32 starts = 1 [packed = true]; - - // Checksum of plaintext content. (i.e., in correct order.) - // - // Each chunk also has a MAC, as generated by GCM, so this is NOT Mac-then-Encrypt, which has - // security implications. This is an additional checksum to verify that once the chunks have - // been reordered, that the file matches the expected plaintext. This prevents the device - // restoring garbage data in case of a mismatch between the ChunkOrdering and the backup blob. - optional bytes checksum = 2; -} - -// Additional metadata about a backup blob that needs to be synced to the server. This is used on -// restore to reconstruct the blob in its correct order. (The chunk order is randomized so as to -// give the server less information about which parts of the backup are changing over time.) This -// data structure is only ever uploaded to the server encrypted with a key unknown to the server. -// Next tag : 6 -message ChunksMetadata { - // Cipher algorithm with which the chunk listing and chunks are encrypted. - optional CipherType cipher_type = 1; - - // Defines the type of chunk order this metadata contains. If unspecified this backup file was - // created before INLINE_LENGTHS was supported, thus assume it is EXPLICIT_STARTS. - optional ChunkOrderingType chunk_ordering_type = 5 - [default = CHUNK_ORDERING_TYPE_UNSPECIFIED]; - - // Encrypted bytes of ChunkOrdering - optional bytes chunk_ordering = 2; - - // The type of algorithm used for the checksum of the plaintext. (See ChunkOrdering.) This is - // for forwards compatibility in case we change the algorithm in the future. For now, always - // SHA-256. - optional ChecksumType checksum_type = 3; - - // This used to be the plaintext tertiary key. No longer used. - reserved 4; -}
\ No newline at end of file diff --git a/packages/BackupEncryption/proto/key_value_listing.proto b/packages/BackupEncryption/proto/key_value_listing.proto deleted file mode 100644 index 001e697bd804..000000000000 --- a/packages/BackupEncryption/proto/key_value_listing.proto +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 - */ - -syntax = "proto2"; - -package android_backup_crypto; - -option java_package = "com.android.server.backup.encryption.protos"; -option java_outer_classname = "KeyValueListingProto"; - -// An entry of a key-value pair. -message KeyValueEntry { - // Plaintext key of the key-value pair. - optional string key = 1; - // SHA-256 MAC of the plaintext of the chunk containing the pair - optional bytes hash = 2; -} - -// Describes the key/value pairs currently in the backup blob, mapping from the -// plaintext key to the hash of the chunk containing the pair. -// -// This is local state stored on the device. It is never sent to the -// backup server. See ChunkOrdering for how the device restores the -// key-value pairs in the correct order. -message KeyValueListing { - repeated KeyValueEntry entries = 1; -} diff --git a/packages/BackupEncryption/proto/key_value_pair.proto b/packages/BackupEncryption/proto/key_value_pair.proto deleted file mode 100644 index 177fa3025dc8..000000000000 --- a/packages/BackupEncryption/proto/key_value_pair.proto +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 - */ - -syntax = "proto2"; - -package android_backup_crypto; - -option java_package = "com.android.server.backup.encryption.protos"; -option java_outer_classname = "KeyValuePairProto"; - -// Serialized form of a key-value pair, when it is to be encrypted in a blob. -// The backup blob for a key-value database consists of repeated encrypted -// key-value pairs like this, in a randomized order. See ChunkOrdering for how -// these are then reconstructed during a restore. -message KeyValuePair { - optional string key = 1; - optional bytes value = 2; -} diff --git a/packages/BackupEncryption/proto/wrapped_key.proto b/packages/BackupEncryption/proto/wrapped_key.proto deleted file mode 100644 index 817b7b40d606..000000000000 --- a/packages/BackupEncryption/proto/wrapped_key.proto +++ /dev/null @@ -1,52 +0,0 @@ -syntax = "proto2"; - -package android_backup_crypto; - -option java_package = "com.android.server.backup.encryption.protos"; -option java_outer_classname = "WrappedKeyProto"; - -// Metadata associated with a tertiary key. -message KeyMetadata { - // Type of Cipher algorithm the key is used for. - enum Type { - UNKNOWN = 0; - // No padding. Uses 12-byte nonce. Tag length 16 bytes. - AES_256_GCM = 1; - } - - // What kind of Cipher algorithm the key is used for. We assume at the moment - // that this will always be AES_256_GCM and throw if this is not the case. - // Provided here for forwards compatibility in case at some point we need to - // change Cipher algorithm. - optional Type type = 1; -} - -// An encrypted tertiary key. -message WrappedKey { - // The Cipher with which the key was encrypted. - enum WrapAlgorithm { - UNKNOWN = 0; - // No padding. Uses 16-byte nonce (see nonce field). Tag length 16 bytes. - // The nonce is 16-bytes as this is wrapped with a key in AndroidKeyStore. - // AndroidKeyStore requires that it generates the IV, and it generates a - // 16-byte IV for you. You CANNOT provide your own IV. - AES_256_GCM = 1; - } - - // Cipher algorithm used to wrap the key. We assume at the moment that this - // is always AES_256_GC and throw if this is not the case. Provided here for - // forwards compatibility if at some point we need to change Cipher algorithm. - optional WrapAlgorithm wrap_algorithm = 1; - - // The nonce used to initialize the Cipher in AES/256/GCM mode. - optional bytes nonce = 2; - - // The encrypted bytes of the key material. - optional bytes key = 3; - - // Associated key metadata. - optional KeyMetadata metadata = 4; - - // Deprecated field; Do not use - reserved 5; -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java deleted file mode 100644 index bb1336ff2b53..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 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.internal.util.Preconditions.checkState; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.security.keystore.recovery.InternalRecoveryServiceException; -import android.security.keystore.recovery.RecoveryController; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; - -import java.security.KeyStoreException; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -/** - * State about encrypted backups that needs to be remembered. - */ -public class CryptoSettings { - - private static final String TAG = "CryptoSettings"; - - private static final String SHARED_PREFERENCES_NAME = "crypto_settings"; - - private static final String KEY_IS_INITIALIZED = "isInitialized"; - private static final String KEY_ACTIVE_SECONDARY_ALIAS = "activeSecondary"; - private static final String KEY_NEXT_SECONDARY_ALIAS = "nextSecondary"; - private static final String SECONDARY_KEY_LAST_ROTATED_AT = "secondaryKeyLastRotatedAt"; - private static final String[] SETTINGS_FOR_BACKUP = { - KEY_IS_INITIALIZED, - KEY_ACTIVE_SECONDARY_ALIAS, - KEY_NEXT_SECONDARY_ALIAS, - SECONDARY_KEY_LAST_ROTATED_AT - }; - - private static final long DEFAULT_SECONDARY_KEY_ROTATION_PERIOD = - TimeUnit.MILLISECONDS.convert(31, TimeUnit.DAYS); - - private static final String KEY_ANCESTRAL_SECONDARY_KEY_VERSION = - "ancestral_secondary_key_version"; - - private final SharedPreferences mSharedPreferences; - private final Context mContext; - - /** - * A new instance. - * - * @param context For looking up the {@link SharedPreferences}, for storing state. - * @return The instance. - */ - public static CryptoSettings getInstance(Context context) { - // We need single process mode because CryptoSettings may be used from several processes - // simultaneously. - SharedPreferences sharedPreferences = - context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - return new CryptoSettings(sharedPreferences, context); - } - - /** - * A new instance using {@link SharedPreferences} in the default mode. - * - * <p>This will not work across multiple processes but will work in tests. - */ - @VisibleForTesting - public static CryptoSettings getInstanceForTesting(Context context) { - SharedPreferences sharedPreferences = - context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); - return new CryptoSettings(sharedPreferences, context); - } - - private CryptoSettings(SharedPreferences sharedPreferences, Context context) { - mSharedPreferences = Objects.requireNonNull(sharedPreferences); - mContext = Objects.requireNonNull(context); - } - - /** - * The alias of the current active secondary key. This should be used to retrieve the key from - * AndroidKeyStore. - */ - public Optional<String> getActiveSecondaryKeyAlias() { - return getStringInSharedPrefs(KEY_ACTIVE_SECONDARY_ALIAS); - } - - /** - * The alias of the secondary key to which the client is rotating. The rotation is not - * immediate, which is why this setting is needed. Once the next key is created, it can take up - * to 72 hours potentially (or longer if the user has no network) for the next key to be synced - * with the keystore. Only after that has happened does the client attempt to re-wrap all - * tertiary keys and commit the rotation. - */ - public Optional<String> getNextSecondaryKeyAlias() { - return getStringInSharedPrefs(KEY_NEXT_SECONDARY_ALIAS); - } - - /** - * If the settings have been initialized. - */ - public boolean getIsInitialized() { - return mSharedPreferences.getBoolean(KEY_IS_INITIALIZED, false); - } - - /** - * Sets the alias of the currently active secondary key. - * - * @param activeAlias The alias, as in AndroidKeyStore. - * @throws IllegalArgumentException if the alias is not in the user's keystore. - */ - public void setActiveSecondaryKeyAlias(String activeAlias) throws IllegalArgumentException { - assertIsValidAlias(activeAlias); - mSharedPreferences.edit().putString(KEY_ACTIVE_SECONDARY_ALIAS, activeAlias).apply(); - } - - /** - * Sets the alias of the secondary key to which the client is rotating. - * - * @param nextAlias The alias, as in AndroidKeyStore. - * @throws KeyStoreException if unable to check whether alias is valid in the keystore. - * @throws IllegalArgumentException if the alias is not in the user's keystore. - */ - public void setNextSecondaryAlias(String nextAlias) throws IllegalArgumentException { - assertIsValidAlias(nextAlias); - mSharedPreferences.edit().putString(KEY_NEXT_SECONDARY_ALIAS, nextAlias).apply(); - } - - /** - * Unsets the alias of the key to which the client is rotating. This is generally performed once - * a rotation is complete. - */ - public void removeNextSecondaryKeyAlias() { - mSharedPreferences.edit().remove(KEY_NEXT_SECONDARY_ALIAS).apply(); - } - - /** - * Sets the timestamp of when the secondary key was last rotated. - * - * @param timestamp The timestamp to set. - */ - public void setSecondaryLastRotated(long timestamp) { - mSharedPreferences.edit().putLong(SECONDARY_KEY_LAST_ROTATED_AT, timestamp).apply(); - } - - /** - * Returns a timestamp of when the secondary key was last rotated. - * - * @return The timestamp. - */ - public Optional<Long> getSecondaryLastRotated() { - if (!mSharedPreferences.contains(SECONDARY_KEY_LAST_ROTATED_AT)) { - return Optional.empty(); - } - return Optional.of(mSharedPreferences.getLong(SECONDARY_KEY_LAST_ROTATED_AT, -1)); - } - - /** - * Sets the settings to have been initialized. (Otherwise loading should try to initialize - * again.) - */ - private void setIsInitialized() { - mSharedPreferences.edit().putBoolean(KEY_IS_INITIALIZED, true).apply(); - } - - /** - * Initializes with the given key alias. - * - * @param alias The secondary key alias to be set as active. - * @throws IllegalArgumentException if the alias does not reference a valid key. - * @throws IllegalStateException if attempting to initialize an already initialized settings. - */ - public void initializeWithKeyAlias(String alias) throws IllegalArgumentException { - checkState( - !getIsInitialized(), "Attempting to initialize an already initialized settings."); - setActiveSecondaryKeyAlias(alias); - setIsInitialized(); - } - - /** Returns the secondary key version of the encrypted backup set to restore from (if set). */ - public Optional<String> getAncestralSecondaryKeyVersion() { - return Optional.ofNullable( - mSharedPreferences.getString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, null)); - } - - /** Sets the secondary key version of the encrypted backup set to restore from. */ - public void setAncestralSecondaryKeyVersion(String ancestralSecondaryKeyVersion) { - mSharedPreferences - .edit() - .putString(KEY_ANCESTRAL_SECONDARY_KEY_VERSION, ancestralSecondaryKeyVersion) - .apply(); - } - - /** The number of milliseconds between secondary key rotation */ - public long backupSecondaryKeyRotationIntervalMs() { - return DEFAULT_SECONDARY_KEY_ROTATION_PERIOD; - } - - /** Deletes all crypto settings related to backup (as opposed to restore). */ - public void clearAllSettingsForBackup() { - Editor sharedPrefsEditor = mSharedPreferences.edit(); - for (String backupSettingKey : SETTINGS_FOR_BACKUP) { - sharedPrefsEditor.remove(backupSettingKey); - } - sharedPrefsEditor.apply(); - - Slog.d(TAG, "Cleared crypto settings for backup"); - } - - /** - * Throws {@link IllegalArgumentException} if the alias does not refer to a key that is in - * the {@link RecoveryController}. - */ - private void assertIsValidAlias(String alias) throws IllegalArgumentException { - try { - if (!RecoveryController.getInstance(mContext).getAliases().contains(alias)) { - throw new IllegalArgumentException(alias + " is not in RecoveryController"); - } - } catch (InternalRecoveryServiceException e) { - throw new IllegalArgumentException("Problem accessing recovery service", e); - } - } - - private Optional<String> getStringInSharedPrefs(String key) { - return Optional.ofNullable(mSharedPreferences.getString(key, null)); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java deleted file mode 100644 index 2035b6605559..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/EncryptionKeyHelper.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullBackupDataProcessor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullBackupDataProcessor.java deleted file mode 100644 index f3ab2bde086a..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullBackupDataProcessor.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.backup.BackupTransport; - -import java.io.IOException; -import java.io.InputStream; - -/** Accepts the full backup data stream and sends it to the server. */ -public interface FullBackupDataProcessor { - /** - * Prepares the upload. - * - * <p>After this, call {@link #start()} to establish the connection. - * - * @param inputStream to read the backup data from, calling {@link #finish} or {@link #cancel} - * will close the stream - * @return {@code true} if the connection was set up successfully, otherwise {@code false} - */ - boolean initiate(InputStream inputStream) throws IOException; - - /** - * Starts the upload, establishing the connection to the server. - * - * <p>After this, call {@link #pushData(int)} to request that the processor reads data from the - * socket, and uploads it to the server. - * - * <p>After this you must call one of {@link #cancel()}, {@link #finish()}, {@link - * #handleCheckSizeRejectionZeroBytes()}, {@link #handleCheckSizeRejectionQuotaExceeded()} or - * {@link #handleSendBytesQuotaExceeded()} to close the upload. - */ - void start(); - - /** - * Requests that the processor read {@code numBytes} from the input stream passed in {@link - * #initiate(InputStream)} and upload them to the server. - * - * @return {@link BackupTransport#TRANSPORT_OK} if the upload succeeds, or {@link - * BackupTransport#TRANSPORT_QUOTA_EXCEEDED} if the upload exceeded the server-side app size - * quota, or {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} for other errors. - */ - int pushData(int numBytes); - - /** Cancels the upload and tears down the connection. */ - void cancel(); - - /** - * Finish the upload and tear down the connection. - * - * <p>Call this after there is no more data to push with {@link #pushData(int)}. - * - * @return One of {@link BackupTransport#TRANSPORT_OK} if the app upload succeeds, {@link - * BackupTransport#TRANSPORT_QUOTA_EXCEEDED} if the upload exceeded the server-side app size - * quota, {@link BackupTransport#TRANSPORT_ERROR} for server 500s, or {@link - * BackupTransport#TRANSPORT_PACKAGE_REJECTED} for other errors. - */ - int finish(); - - /** - * Notifies the processor that the current upload should be terminated because the estimated - * size is zero. - */ - void handleCheckSizeRejectionZeroBytes(); - - /** - * Notifies the processor that the current upload should be terminated because the estimated - * size exceeds the quota. - */ - void handleCheckSizeRejectionQuotaExceeded(); - - /** - * Notifies this class that the current upload should be terminated because the quota was - * exceeded during upload. - */ - void handleSendBytesQuotaExceeded(); - - /** - * Attaches {@link FullBackupCallbacks} which the processor will notify when the backup - * succeeds. - */ - void attachCallbacks(FullBackupCallbacks fullBackupCallbacks); - - /** - * Implemented by the caller of the processor to receive notification of when the backup - * succeeds. - */ - interface FullBackupCallbacks { - /** The processor calls this to indicate that the current backup has succeeded. */ - void onSuccess(); - - /** The processor calls this if the upload failed for a non-transient reason. */ - void onTransferFailed(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDataProcessor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDataProcessor.java deleted file mode 100644 index e4c40491bc2f..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDataProcessor.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 java.io.IOException; - -/** - * Retrieves the data during a full restore, decrypting it if necessary. - * - * <p>Use {@link FullRestoreDataProcessorFactory} to construct the encrypted or unencrypted - * processor as appropriate during restore. - */ -public interface FullRestoreDataProcessor { - /** Return value of {@link #readNextChunk} when there is no more data to download. */ - int END_OF_STREAM = -1; - - /** - * Reads the next chunk of restore data and writes it to the given buffer. - * - * <p>Where necessary, will open the connection to the server and/or decrypt the backup file. - * - * <p>The implementation may retry various errors. If the retries fail it will throw the - * relevant exception. - * - * @return the number of bytes read, or {@link #END_OF_STREAM} if there is no more data - * @throws IOException when downloading from the network or writing to disk - */ - int readNextChunk(byte[] buffer) throws IOException; - - /** - * Closes the connection to the server, deletes any temporary files and optionally sends a log - * with the given finish type. - * - * @param finishType one of {@link FullRestoreDownloader.FinishType} - */ - void finish(FullRestoreDownloader.FinishType finishType); -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java deleted file mode 100644 index afcca79a0027..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/FullRestoreDownloader.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 java.io.IOException; - -/** Interface for classes which will provide backup data */ -public abstract class FullRestoreDownloader { - /** Enum to provide information on why a download finished */ - public enum FinishType { - UNKNOWN_FINISH(0), - // Finish the downloading and successfully write data to Android OS. - FINISHED(1), - // Download failed with any kind of exception. - TRANSFER_FAILURE(2), - // Download failed due to auth failure on the device. - AUTH_FAILURE(3), - // Aborted by Android Framework. - FRAMEWORK_ABORTED(4); - - private int mValue; - - FinishType(int value) { - mValue = value; - } - } - - /** Get the next data chunk from the backing store */ - public abstract int readNextChunk(byte[] buffer) throws IOException; - - /** Called when we've finished restoring the data */ - public abstract void finish(FinishType finishType); -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java deleted file mode 100644 index db2dd2f34ec1..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/KeyValueEncrypter.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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.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 static final String TAG = "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"); - } - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/StreamUtils.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/StreamUtils.java deleted file mode 100644 index 66be25b53a62..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/StreamUtils.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** Utility methods for dealing with Streams */ -public class StreamUtils { - private static final int MAX_COPY_BUFFER_SIZE = 1024; // 1k copy buffer size. - - /** - * Close a Closeable and silently ignore any IOExceptions. - * - * @param closeable The closeable to close - */ - public static void closeQuietly(Closeable closeable) { - try { - closeable.close(); - } catch (IOException ioe) { - // Silently ignore - } - } - - /** - * Copy data from an InputStream to an OutputStream upto a given number of bytes. - * - * @param in The source InputStream - * @param out The destination OutputStream - * @param limit The maximum number of bytes to copy - * @throws IOException Thrown if there is a problem performing the copy. - */ - public static void copyStream(InputStream in, OutputStream out, int limit) throws IOException { - int bufferSize = Math.min(MAX_COPY_BUFFER_SIZE, limit); - byte[] buffer = new byte[bufferSize]; - - int copied = 0; - while (copied < limit) { - int maxReadSize = Math.min(bufferSize, limit - copied); - int read = in.read(buffer, 0, maxReadSize); - if (read < 0) { - return; // Reached the stream end before the limit - } - out.write(buffer, 0, read); - copied += read; - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkHash.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkHash.java deleted file mode 100644 index 1630eb8ff4e8..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkHash.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2018 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.chunk; - -import com.android.internal.util.Preconditions; - -import java.util.Arrays; -import java.util.Base64; - -/** - * Represents the SHA-256 hash of the plaintext of a chunk, which is frequently used as a key. - * - * <p>This class is {@link Comparable} and implements {@link #equals(Object)} and {@link - * #hashCode()}. - */ -public class ChunkHash implements Comparable<ChunkHash> { - /** The length of the hash in bytes. The hash is a SHA-256, so this is 256 bits. */ - public static final int HASH_LENGTH_BYTES = 256 / 8; - - private static final int UNSIGNED_MASK = 0xFF; - - private final byte[] mHash; - - /** Constructs a new instance which wraps the given SHA-256 hash bytes. */ - public ChunkHash(byte[] hash) { - Preconditions.checkArgument(hash.length == HASH_LENGTH_BYTES, "Hash must have 256 bits"); - mHash = hash; - } - - public byte[] getHash() { - return mHash; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ChunkHash)) { - return false; - } - - ChunkHash chunkHash = (ChunkHash) o; - return Arrays.equals(mHash, chunkHash.mHash); - } - - @Override - public int hashCode() { - return Arrays.hashCode(mHash); - } - - @Override - public int compareTo(ChunkHash other) { - return lexicographicalCompareUnsignedBytes(getHash(), other.getHash()); - } - - @Override - public String toString() { - return Base64.getEncoder().encodeToString(mHash); - } - - private static int lexicographicalCompareUnsignedBytes(byte[] left, byte[] right) { - int minLength = Math.min(left.length, right.length); - for (int i = 0; i < minLength; i++) { - int result = toInt(left[i]) - toInt(right[i]); - if (result != 0) { - return result; - } - } - return left.length - right.length; - } - - private static int toInt(byte value) { - return value & UNSIGNED_MASK; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java deleted file mode 100644 index 51d7d532c920..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkListingMap.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.chunk; - -import android.annotation.Nullable; - -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; - -import java.util.HashMap; -import java.util.Map; - -/** - * Chunk listing in a format optimized for quick look up of chunks via their hash keys. This is - * useful when building an incremental backup. After a chunk has been produced, the algorithm can - * quickly look up whether the chunk existed in the previous backup by checking this chunk listing. - * It can then tell the server to use that chunk, through telling it the position and length of the - * chunk in the previous backup's blob. - */ -public class ChunkListingMap { - - private final Map<ChunkHash, Entry> mChunksByHash; - - /** Construct a map from a {@link ChunksMetadataProto.ChunkListing} protobuf */ - public static ChunkListingMap fromProto(ChunksMetadataProto.ChunkListing chunkListingProto) { - Map<ChunkHash, Entry> entries = new HashMap<>(); - - long start = 0; - - for (ChunksMetadataProto.Chunk chunk : chunkListingProto.chunks) { - entries.put(new ChunkHash(chunk.hash), new Entry(start, chunk.length)); - start += chunk.length; - } - - return new ChunkListingMap(entries); - } - - private ChunkListingMap(Map<ChunkHash, Entry> chunksByHash) { - // This is only called from the {@link #fromProto} method, so we don't - // need to take a copy. - this.mChunksByHash = chunksByHash; - } - - /** Returns {@code true} if there is a chunk with the given SHA-256 MAC key in the listing. */ - public boolean hasChunk(ChunkHash hash) { - return mChunksByHash.containsKey(hash); - } - - /** - * Returns the entry for the chunk with the given hash. - * - * @param hash The SHA-256 MAC of the plaintext of the chunk. - * @return The entry, containing position and length of the chunk in the backup blob, or null if - * it does not exist. - */ - @Nullable - public Entry getChunkEntry(ChunkHash hash) { - return mChunksByHash.get(hash); - } - - /** Information about a chunk entry in a backup blob - i.e., its position and length. */ - public static final class Entry { - private final int mLength; - private final long mStart; - - private Entry(long start, int length) { - mLength = length; - mStart = start; - } - - /** Returns the length of the chunk in bytes. */ - public int getLength() { - return mLength; - } - - /** Returns the start position of the chunk in the backup blob, in bytes. */ - public long getStart() { - return mStart; - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java deleted file mode 100644 index 9cda3395f79a..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/ChunkOrderingType.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2018 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.chunk; - -import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED; -import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.EXPLICIT_STARTS; -import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.INLINE_LENGTHS; - -import android.annotation.IntDef; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** IntDef corresponding to the ChunkOrderingType enum in the ChunksMetadataProto protobuf. */ -@IntDef({CHUNK_ORDERING_TYPE_UNSPECIFIED, EXPLICIT_STARTS, INLINE_LENGTHS}) -@Retention(RetentionPolicy.SOURCE) -public @interface ChunkOrderingType {} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java deleted file mode 100644 index edf1b9abb822..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrdering.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2018 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.chunk; - -import java.util.Arrays; - -/** - * Holds the bytes of an encrypted {@link ChunksMetadataProto.ChunkOrdering}. - * - * <p>TODO(b/116575321): After all code is ported, remove the factory method and rename - * encryptedChunkOrdering() to getBytes(). - */ -public class EncryptedChunkOrdering { - /** - * Constructs a new object holding the given bytes of an encrypted {@link - * ChunksMetadataProto.ChunkOrdering}. - * - * <p>Note that this just holds an ordering which is already encrypted, it does not encrypt the - * ordering. - */ - public static EncryptedChunkOrdering create(byte[] encryptedChunkOrdering) { - return new EncryptedChunkOrdering(encryptedChunkOrdering); - } - - private final byte[] mEncryptedChunkOrdering; - - /** Get the encrypted chunk ordering */ - public byte[] encryptedChunkOrdering() { - return mEncryptedChunkOrdering; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof EncryptedChunkOrdering)) { - return false; - } - - EncryptedChunkOrdering encryptedChunkOrdering = (EncryptedChunkOrdering) o; - return Arrays.equals( - mEncryptedChunkOrdering, encryptedChunkOrdering.mEncryptedChunkOrdering); - } - - @Override - public int hashCode() { - return Arrays.hashCode(mEncryptedChunkOrdering); - } - - private EncryptedChunkOrdering(byte[] encryptedChunkOrdering) { - mEncryptedChunkOrdering = encryptedChunkOrdering; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java deleted file mode 100644 index 4010bfd12e03..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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.chunking; - -import static com.android.internal.util.Preconditions.checkArgument; -import static com.android.internal.util.Preconditions.checkState; - -import android.annotation.Nullable; -import android.util.Slog; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunk.ChunkListingMap; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * Writes batches of {@link EncryptedChunk} to a diff script, and generates the associated {@link - * ChunksMetadataProto.ChunkListing} and {@link ChunksMetadataProto.ChunkOrdering}. - */ -public class BackupFileBuilder { - private static final String TAG = "BackupFileBuilder"; - - private static final int BYTES_PER_KILOBYTE = 1024; - - private final BackupWriter mBackupWriter; - private final EncryptedChunkEncoder mEncryptedChunkEncoder; - private final ChunkListingMap mOldChunkListing; - private final ChunksMetadataProto.ChunkListing mNewChunkListing; - private final ChunksMetadataProto.ChunkOrdering mChunkOrdering; - private final List<ChunksMetadataProto.Chunk> mKnownChunks = new ArrayList<>(); - private final List<Integer> mKnownStarts = new ArrayList<>(); - private final Map<ChunkHash, Long> mChunkStartPositions; - - private long mNewChunksSizeBytes; - private boolean mFinished; - - /** - * Constructs a new instance which writes raw data to the given {@link OutputStream}, without - * generating a diff. - * - * <p>This class never closes the output stream. - */ - public static BackupFileBuilder createForNonIncremental(OutputStream outputStream) { - return new BackupFileBuilder( - new RawBackupWriter(outputStream), new ChunksMetadataProto.ChunkListing()); - } - - /** - * Constructs a new instance which writes a diff script to the given {@link OutputStream} using - * a {@link SingleStreamDiffScriptWriter}. - * - * <p>This class never closes the output stream. - * - * @param oldChunkListing against which the diff will be generated. - */ - public static BackupFileBuilder createForIncremental( - OutputStream outputStream, ChunksMetadataProto.ChunkListing oldChunkListing) { - return new BackupFileBuilder( - DiffScriptBackupWriter.newInstance(outputStream), oldChunkListing); - } - - private BackupFileBuilder( - BackupWriter backupWriter, ChunksMetadataProto.ChunkListing oldChunkListing) { - this.mBackupWriter = backupWriter; - // TODO(b/77188289): Use InlineLengthsEncryptedChunkEncoder for key-value backups - this.mEncryptedChunkEncoder = new LengthlessEncryptedChunkEncoder(); - this.mOldChunkListing = ChunkListingMap.fromProto(oldChunkListing); - - mNewChunkListing = new ChunksMetadataProto.ChunkListing(); - mNewChunkListing.cipherType = ChunksMetadataProto.AES_256_GCM; - mNewChunkListing.chunkOrderingType = ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED; - - mChunkOrdering = new ChunksMetadataProto.ChunkOrdering(); - mChunkStartPositions = new HashMap<>(); - } - - /** - * Writes the given chunks to the output stream, and adds them to the new chunk listing and - * chunk ordering. - * - * <p>Sorts the chunks in lexicographical order before writing. - * - * @param allChunks The hashes of all the chunks, in the order they appear in the plaintext. - * @param newChunks A map from hash to {@link EncryptedChunk} containing the new chunks not - * present in the previous backup. - */ - public void writeChunks(List<ChunkHash> allChunks, Map<ChunkHash, EncryptedChunk> newChunks) - throws IOException { - checkState(!mFinished, "Cannot write chunks after flushing."); - - List<ChunkHash> sortedChunks = new ArrayList<>(allChunks); - Collections.sort(sortedChunks); - for (ChunkHash chunkHash : sortedChunks) { - // As we have already included this chunk in the backup file, don't add it again to - // deduplicate identical chunks. - if (!mChunkStartPositions.containsKey(chunkHash)) { - // getBytesWritten() gives us the start of the chunk. - mChunkStartPositions.put(chunkHash, mBackupWriter.getBytesWritten()); - - writeChunkToFileAndListing(chunkHash, newChunks); - } - } - - long totalSizeKb = mBackupWriter.getBytesWritten() / BYTES_PER_KILOBYTE; - long newChunksSizeKb = mNewChunksSizeBytes / BYTES_PER_KILOBYTE; - Slog.d( - TAG, - "Total backup size: " - + totalSizeKb - + " kb, new chunks size: " - + newChunksSizeKb - + " kb"); - - for (ChunkHash chunkHash : allChunks) { - mKnownStarts.add(mChunkStartPositions.get(chunkHash).intValue()); - } - } - - /** - * Returns a new listing for all of the chunks written so far, setting the given fingerprint - * mixer salt (this overrides the {@link ChunksMetadataProto.ChunkListing#fingerprintMixerSalt} - * in the old {@link ChunksMetadataProto.ChunkListing} passed into the - * {@link #BackupFileBuilder). - */ - public ChunksMetadataProto.ChunkListing getNewChunkListing( - @Nullable byte[] fingerprintMixerSalt) { - // TODO: b/141537803 Add check to ensure this is called only once per instance - mNewChunkListing.fingerprintMixerSalt = - fingerprintMixerSalt != null - ? Arrays.copyOf(fingerprintMixerSalt, fingerprintMixerSalt.length) - : new byte[0]; - mNewChunkListing.chunks = mKnownChunks.toArray(new ChunksMetadataProto.Chunk[0]); - return mNewChunkListing; - } - - /** Returns a new ordering for all of the chunks written so far, setting the given checksum. */ - public ChunksMetadataProto.ChunkOrdering getNewChunkOrdering(byte[] checksum) { - // TODO: b/141537803 Add check to ensure this is called only once per instance - mChunkOrdering.starts = new int[mKnownStarts.size()]; - for (int i = 0; i < mKnownStarts.size(); i++) { - mChunkOrdering.starts[i] = mKnownStarts.get(i).intValue(); - } - mChunkOrdering.checksum = Arrays.copyOf(checksum, checksum.length); - return mChunkOrdering; - } - - /** - * Finishes the backup file by writing the chunk metadata and metadata position. - * - * <p>Once this is called, calling {@link #writeChunks(List, Map)} will throw {@link - * IllegalStateException}. - */ - public void finish(ChunksMetadataProto.ChunksMetadata metadata) throws IOException { - Objects.requireNonNull(metadata, "Metadata cannot be null"); - - long startOfMetadata = mBackupWriter.getBytesWritten(); - mBackupWriter.writeBytes(ChunksMetadataProto.ChunksMetadata.toByteArray(metadata)); - mBackupWriter.writeBytes(toByteArray(startOfMetadata)); - - mBackupWriter.flush(); - mFinished = true; - } - - /** - * Checks if the given chunk hash references an existing chunk or a new chunk, and adds this - * chunk to the backup file and new chunk listing. - */ - private void writeChunkToFileAndListing( - ChunkHash chunkHash, Map<ChunkHash, EncryptedChunk> newChunks) throws IOException { - Objects.requireNonNull(chunkHash, "Hash cannot be null"); - - if (mOldChunkListing.hasChunk(chunkHash)) { - ChunkListingMap.Entry oldChunk = mOldChunkListing.getChunkEntry(chunkHash); - mBackupWriter.writeChunk(oldChunk.getStart(), oldChunk.getLength()); - - checkArgument(oldChunk.getLength() >= 0, "Chunk must have zero or positive length"); - addChunk(chunkHash.getHash(), oldChunk.getLength()); - } else if (newChunks.containsKey(chunkHash)) { - EncryptedChunk newChunk = newChunks.get(chunkHash); - mEncryptedChunkEncoder.writeChunkToWriter(mBackupWriter, newChunk); - int length = mEncryptedChunkEncoder.getEncodedLengthOfChunk(newChunk); - mNewChunksSizeBytes += length; - - checkArgument(length >= 0, "Chunk must have zero or positive length"); - addChunk(chunkHash.getHash(), length); - } else { - throw new IllegalArgumentException( - "Chunk did not exist in old chunks or new chunks: " + chunkHash); - } - } - - private void addChunk(byte[] chunkHash, int length) { - ChunksMetadataProto.Chunk chunk = new ChunksMetadataProto.Chunk(); - chunk.hash = Arrays.copyOf(chunkHash, chunkHash.length); - chunk.length = length; - mKnownChunks.add(chunk); - } - - private static byte[] toByteArray(long value) { - // Note that this code needs to stay compatible with GWT, which has known - // bugs when narrowing byte casts of long values occur. - byte[] result = new byte[8]; - for (int i = 7; i >= 0; i--) { - result[i] = (byte) (value & 0xffL); - value >>= 8; - } - return result; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupWriter.java deleted file mode 100644 index baa820cbd558..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupWriter.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import java.io.IOException; - -/** Writes backup data either as a diff script or as raw data, determined by the implementation. */ -public interface BackupWriter { - /** Writes the given bytes to the output. */ - void writeBytes(byte[] bytes) throws IOException; - - /** - * Writes an existing chunk from the previous backup to the output. - * - * <p>Note: not all implementations support this method. - */ - void writeChunk(long start, int length) throws IOException; - - /** Returns the number of bytes written, included bytes copied from the old file. */ - long getBytesWritten(); - - /** - * Indicates that no more bytes or chunks will be written. - * - * <p>After calling this, you may not call {@link #writeBytes(byte[])} or {@link - * #writeChunk(long, int)} - */ - void flush() throws IOException; -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ByteRange.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ByteRange.java deleted file mode 100644 index 004d9e3b45f1..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ByteRange.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import com.android.internal.util.Preconditions; - -/** Representation of a range of bytes to be downloaded. */ -final class ByteRange { - private final long mStart; - private final long mEnd; - - /** Creates a range of bytes which includes {@code mStart} and {@code mEnd}. */ - ByteRange(long start, long end) { - Preconditions.checkArgument(start >= 0); - Preconditions.checkArgument(end >= start); - mStart = start; - mEnd = end; - } - - /** Returns the start of the {@code ByteRange}. The start is included in the range. */ - long getStart() { - return mStart; - } - - /** Returns the end of the {@code ByteRange}. The end is included in the range. */ - long getEnd() { - return mEnd; - } - - /** Returns the number of bytes included in the {@code ByteRange}. */ - int getLength() { - return (int) (mEnd - mStart + 1); - } - - /** Creates a new {@link ByteRange} from {@code mStart} to {@code mEnd + length}. */ - ByteRange extend(long length) { - Preconditions.checkArgument(length > 0); - return new ByteRange(mStart, mEnd + length); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ByteRange)) { - return false; - } - - ByteRange byteRange = (ByteRange) o; - return (mEnd == byteRange.mEnd && mStart == byteRange.mStart); - } - - @Override - public int hashCode() { - int result = 17; - result = 31 * result + (int) (mStart ^ (mStart >>> 32)); - result = 31 * result + (int) (mEnd ^ (mEnd >>> 32)); - return result; - } - - @Override - public String toString() { - return String.format("ByteRange{mStart=%d, mEnd=%d}", mStart, mEnd); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkEncryptor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkEncryptor.java deleted file mode 100644 index 48abc8cc4088..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkEncryptor.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import com.android.server.backup.encryption.chunk.ChunkHash; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.GCMParameterSpec; - -/** Encrypts chunks of a file using AES/GCM. */ -public class ChunkEncryptor { - private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; - private static final int GCM_NONCE_LENGTH_BYTES = 12; - private static final int GCM_TAG_LENGTH_BYTES = 16; - - private final SecretKey mSecretKey; - private final SecureRandom mSecureRandom; - - /** - * A new instance using {@code mSecretKey} to encrypt chunks and {@code mSecureRandom} to - * generate nonces. - */ - public ChunkEncryptor(SecretKey secretKey, SecureRandom secureRandom) { - this.mSecretKey = secretKey; - this.mSecureRandom = secureRandom; - } - - /** - * Transforms {@code plaintext} into an {@link EncryptedChunk}. - * - * @param plaintextHash The hash of the plaintext to encrypt, to attach as the key of the chunk. - * @param plaintext Bytes to encrypt. - * @throws InvalidKeyException If the given secret key is not a valid AES key for decryption. - * @throws IllegalBlockSizeException If the input data cannot be encrypted using - * AES/GCM/NoPadding. This should never be the case. - */ - public EncryptedChunk encrypt(ChunkHash plaintextHash, byte[] plaintext) - throws InvalidKeyException, IllegalBlockSizeException { - byte[] nonce = generateNonce(); - Cipher cipher; - try { - cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init( - Cipher.ENCRYPT_MODE, - mSecretKey, - new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, nonce)); - } catch (NoSuchAlgorithmException - | NoSuchPaddingException - | InvalidAlgorithmParameterException e) { - // This can not happen - AES/GCM/NoPadding is supported. - throw new AssertionError(e); - } - byte[] encryptedBytes; - try { - encryptedBytes = cipher.doFinal(plaintext); - } catch (BadPaddingException e) { - // This can not happen - BadPaddingException can only be thrown in decrypt mode. - throw new AssertionError("Impossible: threw BadPaddingException in encrypt mode."); - } - - return EncryptedChunk.create(/*key=*/ plaintextHash, nonce, encryptedBytes); - } - - private byte[] generateNonce() { - byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES]; - mSecureRandom.nextBytes(nonce); - return nonce; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkHasher.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkHasher.java deleted file mode 100644 index 02d498ccd726..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ChunkHasher.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import com.android.server.backup.encryption.chunk.ChunkHash; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; - -/** Computes the SHA-256 HMAC of a chunk of bytes. */ -public class ChunkHasher { - private static final String MAC_ALGORITHM = "HmacSHA256"; - - private final SecretKey mSecretKey; - - /** Constructs a new hasher which computes the HMAC using the given secret key. */ - public ChunkHasher(SecretKey secretKey) { - this.mSecretKey = secretKey; - } - - /** Returns the SHA-256 over the given bytes. */ - public ChunkHash computeHash(byte[] plaintext) throws InvalidKeyException { - try { - Mac mac = Mac.getInstance(MAC_ALGORITHM); - mac.init(mSecretKey); - return new ChunkHash(mac.doFinal(plaintext)); - } catch (NoSuchAlgorithmException e) { - // This can not happen - AES/GCM/NoPadding is available as part of the framework. - throw new AssertionError(e); - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/Chunker.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/Chunker.java deleted file mode 100644 index c9a6293ed060..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/Chunker.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; - -/** Splits an input stream into chunks, which are to be encrypted separately. */ -public interface Chunker { - /** - * Splits the input stream into chunks. - * - * @param inputStream The input stream. - * @param chunkConsumer A function that processes each chunk as it is produced. - * @throws IOException If there is a problem reading the input stream. - * @throws GeneralSecurityException if the consumer function throws an error. - */ - void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer) - throws IOException, GeneralSecurityException; - - /** Function that consumes chunks. */ - interface ChunkConsumer { - /** - * Invoked for each chunk. - * - * @param chunk Plaintext bytes of chunk. - * @throws GeneralSecurityException if there is an issue encrypting the chunk. - */ - void accept(byte[] chunk) throws GeneralSecurityException; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java deleted file mode 100644 index ae2e150de4bc..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutput.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.chunking; - -import static com.android.internal.util.Preconditions.checkState; - -import android.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.backup.encryption.tasks.DecryptedChunkOutput; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** Writes plaintext chunks to a file, building a digest of the plaintext of the resulting file. */ -public class DecryptedChunkFileOutput implements DecryptedChunkOutput { - @VisibleForTesting static final String DIGEST_ALGORITHM = "SHA-256"; - - private final File mOutputFile; - private final MessageDigest mMessageDigest; - @Nullable private FileOutputStream mFileOutputStream; - private boolean mClosed; - @Nullable private byte[] mDigest; - - /** - * Constructs a new instance which writes chunks to the given file and uses the default message - * digest algorithm. - */ - public DecryptedChunkFileOutput(File outputFile) { - mOutputFile = outputFile; - try { - mMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError( - "Impossible condition: JCE thinks it does not support AES.", e); - } - } - - @Override - public DecryptedChunkOutput open() throws IOException { - checkState(mFileOutputStream == null, "Cannot open twice"); - mFileOutputStream = new FileOutputStream(mOutputFile); - return this; - } - - @Override - public void processChunk(byte[] plaintextBuffer, int length) throws IOException { - checkState(mFileOutputStream != null, "Must open before processing chunks"); - mFileOutputStream.write(plaintextBuffer, /*off=*/ 0, length); - mMessageDigest.update(plaintextBuffer, /*offset=*/ 0, length); - } - - @Override - public byte[] getDigest() { - checkState(mClosed, "Must close before getting mDigest"); - - // After the first call to mDigest() the MessageDigest is reset, thus we must store the - // result. - if (mDigest == null) { - mDigest = mMessageDigest.digest(); - } - return mDigest; - } - - @Override - public void close() throws IOException { - mFileOutputStream.close(); - mClosed = true; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java deleted file mode 100644 index 69fb5cbf606d..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.IOException; -import java.io.OutputStream; - -/** Writes backup data to a diff script, using a {@link SingleStreamDiffScriptWriter}. */ -public class DiffScriptBackupWriter implements BackupWriter { - /** - * The maximum size of a chunk in the diff script. The diff script writer {@code mWriter} will - * buffer this many bytes in memory. - */ - private static final int ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES = 1024 * 1024; - - private final SingleStreamDiffScriptWriter mWriter; - private long mBytesWritten; - - /** - * Constructs a new writer which writes the diff script to the given output stream, using the - * maximum new chunk size {@code ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES}. - */ - public static DiffScriptBackupWriter newInstance(OutputStream outputStream) { - SingleStreamDiffScriptWriter writer = - new SingleStreamDiffScriptWriter( - outputStream, ENCRYPTION_DIFF_SCRIPT_MAX_CHUNK_SIZE_BYTES); - return new DiffScriptBackupWriter(writer); - } - - @VisibleForTesting - DiffScriptBackupWriter(SingleStreamDiffScriptWriter writer) { - mWriter = writer; - } - - @Override - public void writeBytes(byte[] bytes) throws IOException { - for (byte b : bytes) { - mWriter.writeByte(b); - } - - mBytesWritten += bytes.length; - } - - @Override - public void writeChunk(long start, int length) throws IOException { - mWriter.writeChunk(start, length); - mBytesWritten += length; - } - - @Override - public long getBytesWritten() { - return mBytesWritten; - } - - @Override - public void flush() throws IOException { - mWriter.flush(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptWriter.java deleted file mode 100644 index 49d15712d4cc..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/DiffScriptWriter.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import java.io.IOException; -import java.io.OutputStream; - -/** Writer that formats a Diff Script and writes it to an output source. */ -interface DiffScriptWriter { - /** Adds a new byte to the diff script. */ - void writeByte(byte b) throws IOException; - - /** Adds a known chunk to the diff script. */ - void writeChunk(long chunkStart, int chunkLength) throws IOException; - - /** Indicates that no more bytes or chunks will be added to the diff script. */ - void flush() throws IOException; - - interface Factory { - DiffScriptWriter create(OutputStream outputStream); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunk.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunk.java deleted file mode 100644 index cde59fa189de..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunk.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import com.android.internal.util.Preconditions; -import com.android.server.backup.encryption.chunk.ChunkHash; - -import java.util.Arrays; -import java.util.Objects; - -/** - * A chunk of a file encrypted using AES/GCM. - * - * <p>TODO(b/116575321): After all code is ported, remove the factory method and rename - * encryptedBytes(), key() and nonce(). - */ -public class EncryptedChunk { - public static final int KEY_LENGTH_BYTES = ChunkHash.HASH_LENGTH_BYTES; - public static final int NONCE_LENGTH_BYTES = 12; - - /** - * Constructs a new instance with the given key, nonce, and encrypted bytes. - * - * @param key SHA-256 Hmac of the chunk plaintext. - * @param nonce Nonce with which the bytes of the chunk were encrypted. - * @param encryptedBytes Encrypted bytes of the chunk. - */ - public static EncryptedChunk create(ChunkHash key, byte[] nonce, byte[] encryptedBytes) { - Preconditions.checkArgument( - nonce.length == NONCE_LENGTH_BYTES, "Nonce does not have the correct length."); - return new EncryptedChunk(key, nonce, encryptedBytes); - } - - private ChunkHash mKey; - private byte[] mNonce; - private byte[] mEncryptedBytes; - - private EncryptedChunk(ChunkHash key, byte[] nonce, byte[] encryptedBytes) { - mKey = key; - mNonce = nonce; - mEncryptedBytes = encryptedBytes; - } - - /** The SHA-256 Hmac of the plaintext bytes of the chunk. */ - public ChunkHash key() { - return mKey; - } - - /** The nonce with which the chunk was encrypted. */ - public byte[] nonce() { - return mNonce; - } - - /** The encrypted bytes of the chunk. */ - public byte[] encryptedBytes() { - return mEncryptedBytes; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof EncryptedChunk)) { - return false; - } - - EncryptedChunk encryptedChunkOrdering = (EncryptedChunk) o; - return Arrays.equals(mEncryptedBytes, encryptedChunkOrdering.mEncryptedBytes) - && Arrays.equals(mNonce, encryptedChunkOrdering.mNonce) - && mKey.equals(encryptedChunkOrdering.mKey); - } - - @Override - public int hashCode() { - return Objects.hash(mKey, Arrays.hashCode(mNonce), Arrays.hashCode(mEncryptedBytes)); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java deleted file mode 100644 index 16beda32af17..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/EncryptedChunkEncoder.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import com.android.server.backup.encryption.chunk.ChunkOrderingType; - -import java.io.IOException; - -/** Encodes an {@link EncryptedChunk} as bytes to write to the encrypted backup file. */ -public interface EncryptedChunkEncoder { - /** - * Encodes the given chunk and asks the writer to write it. - * - * <p>The chunk will be encoded in the format [nonce]+[encrypted data]. - * - * <p>TODO(b/116575321): Choose a more descriptive method name after the code move is done. - */ - void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException; - - /** - * Returns the length in bytes that this chunk would be if encoded with {@link - * #writeChunkToWriter}. - */ - int getEncodedLengthOfChunk(EncryptedChunk chunk); - - /** - * Returns the {@link ChunkOrderingType} that must be included in the backup file, when using - * this decoder, so that the file may be correctly decoded. - */ - @ChunkOrderingType - int getChunkOrderingType(); -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java deleted file mode 100644 index 6b9be9fd91d3..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoder.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import com.android.server.backup.encryption.chunk.ChunkOrderingType; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; - -import java.io.IOException; - -/** - * Encodes an {@link EncryptedChunk} as bytes, prepending the length of the chunk. - * - * <p>This allows us to decode the backup file during restore without any extra information about - * the boundaries of the chunks. The backup file should contain a chunk ordering in mode {@link - * ChunksMetadataProto#INLINE_LENGTHS}. - * - * <p>We use this implementation during key value backup. - */ -public class InlineLengthsEncryptedChunkEncoder implements EncryptedChunkEncoder { - public static final int BYTES_LENGTH = Integer.SIZE / Byte.SIZE; - - private final LengthlessEncryptedChunkEncoder mLengthlessEncryptedChunkEncoder = - new LengthlessEncryptedChunkEncoder(); - - @Override - public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException { - int length = mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk); - writer.writeBytes(toByteArray(length)); - mLengthlessEncryptedChunkEncoder.writeChunkToWriter(writer, chunk); - } - - @Override - public int getEncodedLengthOfChunk(EncryptedChunk chunk) { - return BYTES_LENGTH + mLengthlessEncryptedChunkEncoder.getEncodedLengthOfChunk(chunk); - } - - @Override - @ChunkOrderingType - public int getChunkOrderingType() { - return ChunksMetadataProto.INLINE_LENGTHS; - } - - /** - * Returns a big-endian representation of {@code value} in a 4-element byte array; equivalent to - * {@code ByteBuffer.allocate(4).putInt(value).array()}. For example, the input value {@code - * 0x12131415} would yield the byte array {@code {0x12, 0x13, 0x14, 0x15}}. - * - * <p>Equivalent to guava's Ints.toByteArray. - */ - static byte[] toByteArray(int value) { - return new byte[] { - (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value - }; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java deleted file mode 100644 index e707350505fb..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoder.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import com.android.server.backup.encryption.chunk.ChunkOrderingType; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; - -import java.io.IOException; - -/** - * Encodes an {@link EncryptedChunk} as bytes without including any information about the length of - * the chunk. - * - * <p>In order for us to decode the backup file during restore it must include a chunk ordering in - * mode {@link ChunksMetadataProto#EXPLICIT_STARTS}, which contains the boundaries of the chunks in - * the encrypted file. This information allows us to decode the backup file and divide it into - * chunks without including the length of each chunk inline. - * - * <p>We use this implementation during full backup. - */ -public class LengthlessEncryptedChunkEncoder implements EncryptedChunkEncoder { - @Override - public void writeChunkToWriter(BackupWriter writer, EncryptedChunk chunk) throws IOException { - writer.writeBytes(chunk.nonce()); - writer.writeBytes(chunk.encryptedBytes()); - } - - @Override - public int getEncodedLengthOfChunk(EncryptedChunk chunk) { - return chunk.nonce().length + chunk.encryptedBytes().length; - } - - @Override - @ChunkOrderingType - public int getChunkOrderingType() { - return ChunksMetadataProto.EXPLICIT_STARTS; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java deleted file mode 100644 index 4aea60121810..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/OutputStreamWrapper.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import java.io.OutputStream; - -/** An interface that wraps one {@link OutputStream} with another for filtration purposes. */ -public interface OutputStreamWrapper { - /** Wraps a given {@link OutputStream}. */ - OutputStream wrap(OutputStream outputStream); -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ProtoStore.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ProtoStore.java deleted file mode 100644 index b0a562cdc16d..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ProtoStore.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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.chunking; - -import android.content.Context; -import android.text.TextUtils; -import android.util.AtomicFile; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.protos.nano.KeyValueListingProto; - -import com.google.protobuf.nano.MessageNano; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Objects; -import java.util.Optional; - -/** - * Stores a nano proto for each package, persisting the proto to disk. - * - * <p>This is used to store {@link ChunksMetadataProto.ChunkListing}. - * - * @param <T> the type of nano proto to store. - */ -public class ProtoStore<T extends MessageNano> { - private static final String CHUNK_LISTING_FOLDER = "backup_chunk_listings"; - private static final String KEY_VALUE_LISTING_FOLDER = "backup_kv_listings"; - - private static final String TAG = "BupEncProtoStore"; - - private final File mStoreFolder; - private final Class<T> mClazz; - - /** Creates a new instance which stores chunk listings at the default location. */ - public static ProtoStore<ChunksMetadataProto.ChunkListing> createChunkListingStore( - Context context) throws IOException { - return new ProtoStore<>( - ChunksMetadataProto.ChunkListing.class, - new File(context.getFilesDir().getAbsoluteFile(), CHUNK_LISTING_FOLDER)); - } - - /** Creates a new instance which stores key value listings in the default location. */ - public static ProtoStore<KeyValueListingProto.KeyValueListing> createKeyValueListingStore( - Context context) throws IOException { - return new ProtoStore<>( - KeyValueListingProto.KeyValueListing.class, - new File(context.getFilesDir().getAbsoluteFile(), KEY_VALUE_LISTING_FOLDER)); - } - - /** - * Creates a new instance which stores protos in the given folder. - * - * @param storeFolder The location where the serialized form is stored. - */ - @VisibleForTesting - ProtoStore(Class<T> clazz, File storeFolder) throws IOException { - mClazz = Objects.requireNonNull(clazz); - mStoreFolder = ensureDirectoryExistsOrThrow(storeFolder); - } - - private static File ensureDirectoryExistsOrThrow(File directory) throws IOException { - if (directory.exists() && !directory.isDirectory()) { - throw new IOException("Store folder already exists, but isn't a directory."); - } - - if (!directory.exists() && !directory.mkdir()) { - throw new IOException("Unable to create store folder."); - } - - return directory; - } - - /** - * Returns the chunk listing for the given package, or {@link Optional#empty()} if no listing - * exists. - */ - public Optional<T> loadProto(String packageName) - throws IOException, IllegalAccessException, InstantiationException, - NoSuchMethodException, InvocationTargetException { - File file = getFileForPackage(packageName); - - if (!file.exists()) { - Slog.d( - TAG, - "No chunk listing existed for " + packageName + ", returning empty listing."); - return Optional.empty(); - } - - AtomicFile protoStore = new AtomicFile(file); - byte[] data = protoStore.readFully(); - - Constructor<T> constructor = mClazz.getDeclaredConstructor(); - T proto = constructor.newInstance(); - MessageNano.mergeFrom(proto, data); - return Optional.of(proto); - } - - /** Saves a proto to disk, associating it with the given package. */ - public void saveProto(String packageName, T proto) throws IOException { - Objects.requireNonNull(proto); - File file = getFileForPackage(packageName); - - try (FileOutputStream os = new FileOutputStream(file)) { - os.write(MessageNano.toByteArray(proto)); - } catch (IOException e) { - Slog.e( - TAG, - "Exception occurred when saving the listing for " - + packageName - + ", deleting saved listing.", - e); - - // If a problem occurred when writing the listing then it might be corrupt, so delete - // it. - file.delete(); - - throw e; - } - } - - /** Deletes the proto for the given package, or does nothing if the package has no proto. */ - public void deleteProto(String packageName) { - File file = getFileForPackage(packageName); - file.delete(); - } - - /** Deletes every proto of this type, for all package names. */ - public void deleteAllProtos() { - File[] files = mStoreFolder.listFiles(); - - // We ensure that the storeFolder exists in the constructor, but check just in case it has - // mysteriously disappeared. - if (files == null) { - return; - } - - for (File file : files) { - file.delete(); - } - } - - private File getFileForPackage(String packageName) { - checkPackageName(packageName); - return new File(mStoreFolder, packageName); - } - - private static void checkPackageName(String packageName) { - if (TextUtils.isEmpty(packageName) || packageName.contains("/")) { - throw new IllegalArgumentException( - "Package name must not contain '/' or be empty: " + packageName); - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/RawBackupWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/RawBackupWriter.java deleted file mode 100644 index b211b0fc9470..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/RawBackupWriter.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import java.io.IOException; -import java.io.OutputStream; - -/** Writes data straight to an output stream. */ -public class RawBackupWriter implements BackupWriter { - private final OutputStream mOutputStream; - private long mBytesWritten; - - /** Constructs a new writer which writes bytes to the given output stream. */ - public RawBackupWriter(OutputStream outputStream) { - this.mOutputStream = outputStream; - } - - @Override - public void writeBytes(byte[] bytes) throws IOException { - mOutputStream.write(bytes); - mBytesWritten += bytes.length; - } - - @Override - public void writeChunk(long start, int length) throws IOException { - throw new UnsupportedOperationException("RawBackupWriter cannot write existing chunks"); - } - - @Override - public long getBytesWritten() { - return mBytesWritten; - } - - @Override - public void flush() throws IOException { - mOutputStream.flush(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java deleted file mode 100644 index 0e4bd58345d5..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriter.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import android.annotation.Nullable; - -import com.android.internal.util.Preconditions; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.util.Locale; - -/** - * A {@link DiffScriptWriter} that writes an entire diff script to a single {@link OutputStream}. - */ -public class SingleStreamDiffScriptWriter implements DiffScriptWriter { - static final byte LINE_SEPARATOR = 0xA; - private static final Charset UTF_8 = Charset.forName("UTF-8"); - - private final int mMaxNewByteChunkSize; - private final OutputStream mOutputStream; - private final byte[] mByteBuffer; - private int mBufferSize = 0; - // Each chunk could be written immediately to the output stream. However, - // it is possible that chunks may overlap. We therefore cache the most recent - // reusable chunk and try to merge it with future chunks. - private ByteRange mReusableChunk; - - public SingleStreamDiffScriptWriter(OutputStream outputStream, int maxNewByteChunkSize) { - mOutputStream = outputStream; - mMaxNewByteChunkSize = maxNewByteChunkSize; - mByteBuffer = new byte[maxNewByteChunkSize]; - } - - @Override - public void writeByte(byte b) throws IOException { - if (mReusableChunk != null) { - writeReusableChunk(); - } - mByteBuffer[mBufferSize++] = b; - if (mBufferSize == mMaxNewByteChunkSize) { - writeByteBuffer(); - } - } - - @Override - public void writeChunk(long chunkStart, int chunkLength) throws IOException { - Preconditions.checkArgument(chunkStart >= 0); - Preconditions.checkArgument(chunkLength > 0); - if (mBufferSize != 0) { - writeByteBuffer(); - } - - if (mReusableChunk != null && mReusableChunk.getEnd() + 1 == chunkStart) { - // The new chunk overlaps the old, so combine them into a single byte range. - mReusableChunk = mReusableChunk.extend(chunkLength); - } else { - writeReusableChunk(); - mReusableChunk = new ByteRange(chunkStart, chunkStart + chunkLength - 1); - } - } - - @Override - public void flush() throws IOException { - Preconditions.checkState(!(mBufferSize != 0 && mReusableChunk != null)); - if (mBufferSize != 0) { - writeByteBuffer(); - } - if (mReusableChunk != null) { - writeReusableChunk(); - } - mOutputStream.flush(); - } - - private void writeByteBuffer() throws IOException { - mOutputStream.write(Integer.toString(mBufferSize).getBytes(UTF_8)); - mOutputStream.write(LINE_SEPARATOR); - mOutputStream.write(mByteBuffer, 0, mBufferSize); - mOutputStream.write(LINE_SEPARATOR); - mBufferSize = 0; - } - - private void writeReusableChunk() throws IOException { - if (mReusableChunk != null) { - mOutputStream.write( - String.format( - Locale.US, - "%d-%d", - mReusableChunk.getStart(), - mReusableChunk.getEnd()) - .getBytes(UTF_8)); - mOutputStream.write(LINE_SEPARATOR); - mReusableChunk = null; - } - } - - /** A factory that creates {@link SingleStreamDiffScriptWriter}s. */ - public static class Factory implements DiffScriptWriter.Factory { - private final int mMaxNewByteChunkSize; - private final OutputStreamWrapper mOutputStreamWrapper; - - public Factory(int maxNewByteChunkSize, @Nullable OutputStreamWrapper outputStreamWrapper) { - mMaxNewByteChunkSize = maxNewByteChunkSize; - mOutputStreamWrapper = outputStreamWrapper; - } - - @Override - public SingleStreamDiffScriptWriter create(OutputStream outputStream) { - if (mOutputStreamWrapper != null) { - outputStream = mOutputStreamWrapper.wrap(outputStream); - } - return new SingleStreamDiffScriptWriter(outputStream, mMaxNewByteChunkSize); - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java deleted file mode 100644 index 18011f620b24..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunker.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -import static com.android.internal.util.Preconditions.checkArgument; - -import com.android.server.backup.encryption.chunking.Chunker; - -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.util.Arrays; - -/** Splits a stream of bytes into variable-sized chunks, using content-defined chunking. */ -public class ContentDefinedChunker implements Chunker { - private static final int WINDOW_SIZE = 31; - private static final byte DEFAULT_OUT_BYTE = (byte) 0; - - private final byte[] mChunkBuffer; - private final RabinFingerprint64 mRabinFingerprint64; - private final FingerprintMixer mFingerprintMixer; - private final BreakpointPredicate mBreakpointPredicate; - private final int mMinChunkSize; - private final int mMaxChunkSize; - - /** - * Constructor. - * - * @param minChunkSize The minimum size of a chunk. No chunk will be produced of a size smaller - * than this except possibly at the very end of the stream. - * @param maxChunkSize The maximum size of a chunk. No chunk will be produced of a larger size. - * @param rabinFingerprint64 Calculates fingerprints, with which to determine breakpoints. - * @param breakpointPredicate Given a Rabin fingerprint, returns whether this ought to be a - * breakpoint. - */ - public ContentDefinedChunker( - int minChunkSize, - int maxChunkSize, - RabinFingerprint64 rabinFingerprint64, - FingerprintMixer fingerprintMixer, - BreakpointPredicate breakpointPredicate) { - checkArgument( - minChunkSize >= WINDOW_SIZE, - "Minimum chunk size must be greater than window size."); - checkArgument( - maxChunkSize >= minChunkSize, - "Maximum chunk size cannot be smaller than minimum chunk size."); - mChunkBuffer = new byte[maxChunkSize]; - mRabinFingerprint64 = rabinFingerprint64; - mBreakpointPredicate = breakpointPredicate; - mFingerprintMixer = fingerprintMixer; - mMinChunkSize = minChunkSize; - mMaxChunkSize = maxChunkSize; - } - - /** - * Breaks the input stream into variable-sized chunks. - * - * @param inputStream The input bytes to break into chunks. - * @param chunkConsumer A function to process each chunk as it's generated. - * @throws IOException Thrown if there is an issue reading from the input stream. - * @throws GeneralSecurityException Thrown if the {@link ChunkConsumer} throws it. - */ - @Override - public void chunkify(InputStream inputStream, ChunkConsumer chunkConsumer) - throws IOException, GeneralSecurityException { - int chunkLength; - int initialReadLength = mMinChunkSize - WINDOW_SIZE; - - // Performance optimization - there is no reason to calculate fingerprints for windows - // ending before the minimum chunk size. - while ((chunkLength = - inputStream.read(mChunkBuffer, /*off=*/ 0, /*len=*/ initialReadLength)) - != -1) { - int b; - long fingerprint = 0L; - - while ((b = inputStream.read()) != -1) { - byte inByte = (byte) b; - byte outByte = getCurrentWindowStartByte(chunkLength); - mChunkBuffer[chunkLength++] = inByte; - - fingerprint = - mRabinFingerprint64.computeFingerprint64(inByte, outByte, fingerprint); - - if (chunkLength >= mMaxChunkSize - || (chunkLength >= mMinChunkSize - && mBreakpointPredicate.isBreakpoint( - mFingerprintMixer.mix(fingerprint)))) { - chunkConsumer.accept(Arrays.copyOf(mChunkBuffer, chunkLength)); - chunkLength = 0; - break; - } - } - - if (chunkLength > 0) { - chunkConsumer.accept(Arrays.copyOf(mChunkBuffer, chunkLength)); - } - } - } - - private byte getCurrentWindowStartByte(int chunkLength) { - if (chunkLength < mMinChunkSize) { - return DEFAULT_OUT_BYTE; - } else { - return mChunkBuffer[chunkLength - WINDOW_SIZE]; - } - } - - /** Whether the current fingerprint indicates the end of a chunk. */ - public interface BreakpointPredicate { - - /** - * Returns {@code true} if the fingerprint of the last {@code WINDOW_SIZE} bytes indicates - * the chunk ought to end at this position. - * - * @param fingerprint Fingerprint of the last {@code WINDOW_SIZE} bytes. - * @return Whether this ought to be a chunk breakpoint. - */ - boolean isBreakpoint(long fingerprint); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java deleted file mode 100644 index e9f30505c112..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixer.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -import static com.android.internal.util.Preconditions.checkArgument; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; - -import javax.crypto.SecretKey; - -/** - * Helper for mixing fingerprint with key material. - * - * <p>We do this as otherwise the Rabin fingerprint leaks information about the plaintext. i.e., if - * two users have the same file, it will be partitioned by Rabin in the same way, allowing us to - * infer that it is the same as another user's file. - * - * <p>By mixing the fingerprint with the user's secret key, the chunking method is different on a - * per key basis. Each application has its own {@link SecretKey}, so we cannot infer that a file is - * the same even across multiple applications owned by the same user, never mind across multiple - * users. - * - * <p>Instead of directly mixing the fingerprint with the user's secret, we first securely and - * deterministically derive a secondary chunking key. As Rabin is not a cryptographically secure - * hash, it might otherwise leak information about the user's secret. This prevents that from - * happening. - */ -public class FingerprintMixer { - public static final int SALT_LENGTH_BYTES = 256 / Byte.SIZE; - private static final String DERIVED_KEY_NAME = "RabinFingerprint64Mixer"; - - private final long mAddend; - private final long mMultiplicand; - - /** - * A new instance from a given secret key and salt. Salt must be the same across incremental - * backups, or a different chunking strategy will be used each time, defeating the dedup. - * - * @param secretKey The application-specific secret. - * @param salt The salt. - * @throws InvalidKeyException If the encoded form of {@code secretKey} is inaccessible. - */ - public FingerprintMixer(SecretKey secretKey, byte[] salt) throws InvalidKeyException { - checkArgument(salt.length == SALT_LENGTH_BYTES, "Requires a 256-bit salt."); - byte[] keyBytes = secretKey.getEncoded(); - if (keyBytes == null) { - throw new InvalidKeyException("SecretKey must support encoding for FingerprintMixer."); - } - byte[] derivedKey = - Hkdf.hkdf(keyBytes, salt, DERIVED_KEY_NAME.getBytes(StandardCharsets.UTF_8)); - ByteBuffer buffer = ByteBuffer.wrap(derivedKey); - mAddend = buffer.getLong(); - // Multiplicand must be odd - otherwise we lose some bits of the Rabin fingerprint when - // mixing - mMultiplicand = buffer.getLong() | 1; - } - - /** - * Mixes the fingerprint with the derived key material. This is performed by adding part of the - * derived key and multiplying by another part of the derived key (which is forced to be odd, so - * that the operation is reversible). - * - * @param fingerprint A 64-bit Rabin fingerprint. - * @return The mixed fingerprint. - */ - long mix(long fingerprint) { - return ((fingerprint + mAddend) * mMultiplicand); - } - - /** The addend part of the derived key. */ - long getAddend() { - return mAddend; - } - - /** The multiplicand part of the derived key. */ - long getMultiplicand() { - return mMultiplicand; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java deleted file mode 100644 index d0776aef78b6..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Objects; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -/** - * Secure HKDF utils. Allows client to deterministically derive additional key material from a base - * secret. If the derived key material is compromised, this does not in of itself compromise the - * root secret. - * - * <p>TODO(b/116575321): After all code is ported, rename this class to HkdfUtils. - */ -public final class Hkdf { - private static final byte[] CONSTANT_01 = {0x01}; - private static final String HmacSHA256 = "HmacSHA256"; - private static final String AES = "AES"; - - /** - * Implements HKDF (RFC 5869) with the SHA-256 hash and a 256-bit output key length. - * - * <p>IMPORTANT: The use or edit of this method requires a security review. - * - * @param mainKey Main key from which to derive sub-keys. - * @param salt A randomly generated 256-bit byte string. - * @param data Arbitrary information that is bound to the derived key (i.e., used in its - * creation). - * @return Raw derived key bytes = HKDF-SHA256(mainKey, salt, data). - * @throws InvalidKeyException If the salt can not be used as a valid key. - */ - static byte[] hkdf(byte[] mainKey, byte[] salt, byte[] data) throws InvalidKeyException { - Objects.requireNonNull(mainKey, "HKDF requires main key to be set."); - Objects.requireNonNull(salt, "HKDF requires a salt."); - Objects.requireNonNull(data, "No data provided to HKDF."); - return hkdfSha256Expand(hkdfSha256Extract(mainKey, salt), data); - } - - private Hkdf() {} - - /** - * The HKDF (RFC 5869) extraction function, using the SHA-256 hash function. This function is - * used to pre-process the {@code inputKeyMaterial} and mix it with the {@code salt}, producing - * output suitable for use with HKDF expansion function (which produces the actual derived key). - * - * <p>IMPORTANT: The use or edit of this method requires a security review. - * - * @see #hkdfSha256Expand(byte[], byte[]) - * @return HMAC-SHA256(salt, inputKeyMaterial) (salt is the "key" for the HMAC) - * @throws InvalidKeyException If the salt can not be used as a valid key. - */ - private static byte[] hkdfSha256Extract(byte[] inputKeyMaterial, byte[] salt) - throws InvalidKeyException { - // Note that the SecretKey encoding format is defined to be RAW, so the encoded form should - // be consistent across implementations. - Mac sha256; - try { - sha256 = Mac.getInstance(HmacSHA256); - } catch (NoSuchAlgorithmException e) { - // This can not happen - HmacSHA256 is supported by the platform. - throw new AssertionError(e); - } - sha256.init(new SecretKeySpec(salt, AES)); - - return sha256.doFinal(inputKeyMaterial); - } - - /** - * Special case of HKDF (RFC 5869) expansion function, using the SHA-256 hash function and - * allowing for a maximum output length of 256 bits. - * - * <p>IMPORTANT: The use or edit of this method requires a security review. - * - * @param pseudoRandomKey Generated by {@link #hkdfSha256Extract(byte[], byte[])}. - * @param info Arbitrary information the derived key should be bound to. - * @return Raw derived key bytes = HMAC-SHA256(pseudoRandomKey, info | 0x01). - * @throws InvalidKeyException If the salt can not be used as a valid key. - */ - private static byte[] hkdfSha256Expand(byte[] pseudoRandomKey, byte[] info) - throws InvalidKeyException { - // Note that RFC 5869 computes number of blocks N = ceil(hash length / output length), but - // here we only deal with a 256 bit hash up to a 256 bit output, yielding N=1. - Mac sha256; - try { - sha256 = Mac.getInstance(HmacSHA256); - } catch (NoSuchAlgorithmException e) { - // This can not happen - HmacSHA256 is supported by the platform. - throw new AssertionError(e); - } - sha256.init(new SecretKeySpec(pseudoRandomKey, AES)); - - sha256.update(info); - sha256.update(CONSTANT_01); - return sha256.doFinal(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java deleted file mode 100644 index e867e7c1b801..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpoint.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -import static com.android.internal.util.Preconditions.checkArgument; - -import com.android.server.backup.encryption.chunking.cdc.ContentDefinedChunker.BreakpointPredicate; - -/** - * Function to determine whether a 64-bit fingerprint ought to be a chunk breakpoint. - * - * <p>This works by checking whether there are at least n leading zeros in the fingerprint. n is - * calculated to on average cause a breakpoint after a given number of trials (provided in the - * constructor). This allows us to choose a number of trials that gives a desired average chunk - * size. This works because the fingerprint is pseudo-randomly distributed. - */ -public class IsChunkBreakpoint implements BreakpointPredicate { - private final int mLeadingZeros; - private final long mBitmask; - - /** - * A new instance that causes a breakpoint after a given number of trials on average. - * - * @param averageNumberOfTrialsUntilBreakpoint The number of trials after which on average to - * create a new chunk. If this is not a power of 2, some precision is sacrificed (i.e., on - * average, breaks will actually happen after the nearest power of 2 to the average number - * of trials passed in). - */ - public IsChunkBreakpoint(long averageNumberOfTrialsUntilBreakpoint) { - checkArgument( - averageNumberOfTrialsUntilBreakpoint >= 0, - "Average number of trials must be non-negative"); - - // Want n leading zeros after t trials. - // P(leading zeros = n) = 1/2^n - // Expected num trials to get n leading zeros = 1/2^-n - // t = 1/2^-n - // n = log2(t) - mLeadingZeros = (int) Math.round(log2(averageNumberOfTrialsUntilBreakpoint)); - mBitmask = ~(~0L >>> mLeadingZeros); - } - - /** - * Returns {@code true} if {@code fingerprint} indicates that there should be a chunk - * breakpoint. - */ - @Override - public boolean isBreakpoint(long fingerprint) { - return (fingerprint & mBitmask) == 0; - } - - /** Returns the number of leading zeros in the fingerprint that causes a breakpoint. */ - public int getLeadingZeros() { - return mLeadingZeros; - } - - /** - * Calculates log base 2 of x. Not the most efficient possible implementation, but it's simple, - * obviously correct, and is only invoked on object construction. - */ - private static double log2(double x) { - return Math.log(x) / Math.log(2); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java deleted file mode 100644 index 1e14ffa5ad77..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -/** Helper to calculate a 64-bit Rabin fingerprint over a 31-byte window. */ -public class RabinFingerprint64 { - private static final long DEFAULT_IRREDUCIBLE_POLYNOMIAL_64 = 0x000000000000001BL; - private static final int POLYNOMIAL_DEGREE = 64; - private static final int SLIDING_WINDOW_SIZE_BYTES = 31; - - private final long mPoly64; - // Auxiliary tables to speed up the computation of Rabin fingerprints. - private final long[] mTableFP64 = new long[256]; - private final long[] mTableOutByte = new long[256]; - - /** - * Constructs a new instance over the given irreducible 64-degree polynomial. It is up to the - * caller to determine that the polynomial is irreducible. If it is not the fingerprinting will - * not behave as expected. - * - * @param poly64 The polynomial. - */ - public RabinFingerprint64(long poly64) { - mPoly64 = poly64; - } - - /** Constructs a new instance using {@code x^64 + x^4 + x + 1} as the irreducible polynomial. */ - public RabinFingerprint64() { - this(DEFAULT_IRREDUCIBLE_POLYNOMIAL_64); - computeFingerprintTables64(); - computeFingerprintTables64Windowed(); - } - - /** - * Computes the fingerprint for the new sliding window given the fingerprint of the previous - * sliding window, the byte sliding in, and the byte sliding out. - * - * @param inChar The new char coming into the sliding window. - * @param outChar The left most char sliding out of the window. - * @param fingerPrint Fingerprint for previous window. - * @return New fingerprint for the new sliding window. - */ - public long computeFingerprint64(byte inChar, byte outChar, long fingerPrint) { - return (fingerPrint << 8) - ^ (inChar & 0xFF) - ^ mTableFP64[(int) (fingerPrint >>> 56)] - ^ mTableOutByte[outChar & 0xFF]; - } - - /** Compute auxiliary tables to speed up the fingerprint computation. */ - private void computeFingerprintTables64() { - long[] degreesRes64 = new long[POLYNOMIAL_DEGREE]; - degreesRes64[0] = mPoly64; - for (int i = 1; i < POLYNOMIAL_DEGREE; i++) { - if ((degreesRes64[i - 1] & (1L << 63)) == 0) { - degreesRes64[i] = degreesRes64[i - 1] << 1; - } else { - degreesRes64[i] = (degreesRes64[i - 1] << 1) ^ mPoly64; - } - } - for (int i = 0; i < 256; i++) { - int currIndex = i; - for (int j = 0; (currIndex > 0) && (j < 8); j++) { - if ((currIndex & 0x1) == 1) { - mTableFP64[i] ^= degreesRes64[j]; - } - currIndex >>>= 1; - } - } - } - - /** - * Compute auxiliary table {@code mTableOutByte} to facilitate the computing of fingerprints for - * sliding windows. This table is to take care of the effect on the fingerprint when the - * leftmost byte in the window slides out. - */ - private void computeFingerprintTables64Windowed() { - // Auxiliary array degsRes64[8] defined by: <code>degsRes64[i] = x^(8 * - // SLIDING_WINDOW_SIZE_BYTES + i) mod this.mPoly64.</code> - long[] degsRes64 = new long[8]; - degsRes64[0] = mPoly64; - for (int i = 65; i < 8 * (SLIDING_WINDOW_SIZE_BYTES + 1); i++) { - if ((degsRes64[(i - 1) % 8] & (1L << 63)) == 0) { - degsRes64[i % 8] = degsRes64[(i - 1) % 8] << 1; - } else { - degsRes64[i % 8] = (degsRes64[(i - 1) % 8] << 1) ^ mPoly64; - } - } - for (int i = 0; i < 256; i++) { - int currIndex = i; - for (int j = 0; (currIndex > 0) && (j < 8); j++) { - if ((currIndex & 0x1) == 1) { - mTableOutByte[i] ^= degsRes64[j]; - } - currIndex >>>= 1; - } - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/client/CryptoBackupServer.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/client/CryptoBackupServer.java deleted file mode 100644 index d7f7dc7d0472..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/client/CryptoBackupServer.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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; - -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import java.util.Map; - -/** - * Contains methods for communicating with the parts of the backup server relevant to encryption. - */ -public interface CryptoBackupServer { - /** - * Uploads an incremental backup to the server. - * - * <p>Handles setting up and tearing down the connection. - * - * @param packageName the package to associate the data with - * @param oldDocId the id of the previous backup doc in Drive - * @param diffScript containing the actual backup data - * @param tertiaryKey the wrapped key used to encrypt this backup - * @return the id of the new backup doc in Drive. - */ - String uploadIncrementalBackup( - String packageName, - String oldDocId, - byte[] diffScript, - WrappedKeyProto.WrappedKey tertiaryKey); - - /** - * Uploads non-incremental backup to the server. - * - * <p>Handles setting up and tearing down the connection. - * - * @param packageName the package to associate the data with - * @param data the actual backup data - * @param tertiaryKey the wrapped key used to encrypt this backup - * @return the id of the new backup doc in Drive. - */ - String uploadNonIncrementalBackup( - String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey); - - /** - * Sets the alias of the active secondary key. This is the alias used to refer to the key in the - * {@link java.security.KeyStore}. It is also used to key storage for tertiary keys on the - * backup server. Also has to upload all existing tertiary keys, wrapped with the new key. - * - * @param keyAlias The ID of the secondary key. - * @param tertiaryKeys The tertiary keys, wrapped with the new secondary key. - */ - void setActiveSecondaryKeyAlias( - String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys); -} 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 deleted file mode 100644 index 9e31385c9525..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/client/UnexpectedActiveSecondaryOnServerException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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/keys/KeyWrapUtils.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java deleted file mode 100644 index a043c1fe687f..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/KeyWrapUtils.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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.keys; - -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Locale; - -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.GCMParameterSpec; - -/** Utility functions for wrapping and unwrapping tertiary keys. */ -public class KeyWrapUtils { - private static final String AES_GCM_MODE = "AES/GCM/NoPadding"; - private static final int GCM_TAG_LENGTH_BYTES = 16; - private static final int BITS_PER_BYTE = 8; - private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE; - private static final String KEY_ALGORITHM = "AES"; - - /** - * Uses the secondary key to unwrap the wrapped tertiary key. - * - * @param secondaryKey The secondary key used to wrap the tertiary key. - * @param wrappedKey The wrapped tertiary key. - * @return The unwrapped tertiary key. - * @throws InvalidKeyException if the provided secondary key cannot unwrap the tertiary key. - */ - public static SecretKey unwrap(SecretKey secondaryKey, WrappedKeyProto.WrappedKey wrappedKey) - throws InvalidKeyException, NoSuchAlgorithmException, - InvalidAlgorithmParameterException, NoSuchPaddingException { - if (wrappedKey.wrapAlgorithm != WrappedKeyProto.WrappedKey.AES_256_GCM) { - throw new InvalidKeyException( - String.format( - Locale.US, - "Could not unwrap key wrapped with %s algorithm", - wrappedKey.wrapAlgorithm)); - } - - if (wrappedKey.metadata == null) { - throw new InvalidKeyException("Metadata missing from wrapped tertiary key."); - } - - if (wrappedKey.metadata.type != WrappedKeyProto.KeyMetadata.AES_256_GCM) { - throw new InvalidKeyException( - String.format( - Locale.US, - "Wrapped key was unexpected %s algorithm. Only support" - + " AES/GCM/NoPadding.", - wrappedKey.metadata.type)); - } - - Cipher cipher = getCipher(); - - cipher.init( - Cipher.UNWRAP_MODE, - secondaryKey, - new GCMParameterSpec(GCM_TAG_LENGTH_BITS, wrappedKey.nonce)); - - return (SecretKey) cipher.unwrap(wrappedKey.key, KEY_ALGORITHM, Cipher.SECRET_KEY); - } - - /** - * Wraps the tertiary key with the secondary key. - * - * @param secondaryKey The secondary key to use for wrapping. - * @param tertiaryKey The key to wrap. - * @return The wrapped key. - * @throws InvalidKeyException if the key is not good for wrapping. - * @throws IllegalBlockSizeException if there is an issue wrapping. - */ - public static WrappedKeyProto.WrappedKey wrap(SecretKey secondaryKey, SecretKey tertiaryKey) - throws InvalidKeyException, IllegalBlockSizeException, NoSuchAlgorithmException, - NoSuchPaddingException { - Cipher cipher = getCipher(); - cipher.init(Cipher.WRAP_MODE, secondaryKey); - - WrappedKeyProto.WrappedKey wrappedKey = new WrappedKeyProto.WrappedKey(); - wrappedKey.key = cipher.wrap(tertiaryKey); - wrappedKey.nonce = cipher.getIV(); - wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.AES_256_GCM; - wrappedKey.metadata = new WrappedKeyProto.KeyMetadata(); - wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.AES_256_GCM; - return wrappedKey; - } - - /** - * Rewraps a tertiary key with a new secondary key. - * - * @param oldSecondaryKey The old secondary key, used to unwrap the tertiary key. - * @param newSecondaryKey The new secondary key, used to rewrap the tertiary key. - * @param tertiaryKey The tertiary key, wrapped by {@code oldSecondaryKey}. - * @return The tertiary key, wrapped by {@code newSecondaryKey}. - * @throws InvalidKeyException if the key is not good for wrapping or unwrapping. - * @throws IllegalBlockSizeException if there is an issue wrapping. - */ - public static WrappedKeyProto.WrappedKey rewrap( - SecretKey oldSecondaryKey, - SecretKey newSecondaryKey, - WrappedKeyProto.WrappedKey tertiaryKey) - throws InvalidKeyException, IllegalBlockSizeException, - InvalidAlgorithmParameterException, NoSuchAlgorithmException, - NoSuchPaddingException { - return wrap(newSecondaryKey, unwrap(oldSecondaryKey, tertiaryKey)); - } - - private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException { - return Cipher.getInstance(AES_GCM_MODE); - } - - // Statics only - private KeyWrapUtils() {} -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java deleted file mode 100644 index 436c6de8c2bb..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2018 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.keys; - -import android.annotation.IntDef; -import android.content.Context; -import android.security.keystore.recovery.InternalRecoveryServiceException; -import android.security.keystore.recovery.RecoveryController; -import android.util.Slog; - -import java.util.Objects; - -import javax.crypto.SecretKey; - -/** - * Wraps a {@link RecoveryController}'s {@link SecretKey}. These are kept in "AndroidKeyStore" (a - * provider for {@link java.security.KeyStore} and {@link javax.crypto.KeyGenerator}. They are also - * synced with the recoverable key store, wrapped by the primary key. This allows them to be - * recovered on a user's subsequent device through providing their lock screen secret. - */ -public class RecoverableKeyStoreSecondaryKey { - private static final String TAG = "RecoverableKeyStoreSecondaryKey"; - - private final String mAlias; - private final SecretKey mSecretKey; - - /** - * A new instance. - * - * @param alias The alias. It is keyed with this in AndroidKeyStore and the recoverable key - * store. - * @param secretKey The key. - */ - public RecoverableKeyStoreSecondaryKey(String alias, SecretKey secretKey) { - mAlias = Objects.requireNonNull(alias); - mSecretKey = Objects.requireNonNull(secretKey); - } - - /** - * The ID, as stored in the recoverable {@link java.security.KeyStore}, and as used to identify - * wrapped tertiary keys on the backup server. - */ - public String getAlias() { - return mAlias; - } - - /** The secret key, to be used to wrap tertiary keys. */ - public SecretKey getSecretKey() { - return mSecretKey; - } - - /** - * The status of the key. i.e., whether it's been synced to remote trusted hardware. - * - * @param context The application context. - * @return One of {@link Status#SYNCED}, {@link Status#NOT_SYNCED} or {@link Status#DESTROYED}. - */ - public @Status int getStatus(Context context) { - try { - return getStatusInternal(context); - } catch (InternalRecoveryServiceException e) { - Slog.wtf(TAG, "Internal error getting recovery status", e); - // Return NOT_SYNCED by default, as we do not want the backups to fail or to repeatedly - // attempt to reinitialize. - return Status.NOT_SYNCED; - } - } - - private @Status int getStatusInternal(Context context) throws InternalRecoveryServiceException { - int status = RecoveryController.getInstance(context).getRecoveryStatus(mAlias); - switch (status) { - case RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE: - return Status.DESTROYED; - case RecoveryController.RECOVERY_STATUS_SYNCED: - return Status.SYNCED; - case RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS: - return Status.NOT_SYNCED; - default: - // Throw an exception if we encounter a status that doesn't match any of the above. - throw new InternalRecoveryServiceException( - "Unexpected status from getRecoveryStatus: " + status); - } - } - - /** Status of a key in the recoverable key store. */ - @IntDef({Status.NOT_SYNCED, Status.SYNCED, Status.DESTROYED}) - public @interface Status { - /** - * The key has not yet been synced to remote trusted hardware. This may be because the user - * has not yet unlocked their device. - */ - int NOT_SYNCED = 1; - - /** - * The key has been synced with remote trusted hardware. It should now be recoverable on - * another device. - */ - int SYNCED = 2; - - /** The key has been lost forever. This can occur if the user disables their lock screen. */ - int DESTROYED = 3; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java deleted file mode 100644 index c89076b9928f..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManager.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2018 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.keys; - -import android.content.Context; -import android.security.keystore.recovery.InternalRecoveryServiceException; -import android.security.keystore.recovery.LockScreenRequiredException; -import android.security.keystore.recovery.RecoveryController; - -import com.android.internal.annotations.VisibleForTesting; - -import libcore.util.HexEncoding; - -import java.security.SecureRandom; -import java.security.UnrecoverableKeyException; -import java.util.Optional; - -import javax.crypto.SecretKey; - -/** - * Manages generating, deleting, and retrieving secondary keys through {@link RecoveryController}. - * - * <p>The recoverable key store will be synced remotely via the {@link RecoveryController}, allowing - * recovery of keys on other devices owned by the user. - */ -public class RecoverableKeyStoreSecondaryKeyManager { - private static final String BACKUP_KEY_ALIAS_PREFIX = - "com.android.server.backup/recoverablekeystore/"; - private static final int BACKUP_KEY_SUFFIX_LENGTH_BITS = 128; - private static final int BITS_PER_BYTE = 8; - - /** A new instance. */ - public static RecoverableKeyStoreSecondaryKeyManager getInstance(Context context) { - return new RecoverableKeyStoreSecondaryKeyManager( - RecoveryController.getInstance(context), new SecureRandom()); - } - - private final RecoveryController mRecoveryController; - private final SecureRandom mSecureRandom; - - @VisibleForTesting - public RecoverableKeyStoreSecondaryKeyManager( - RecoveryController recoveryController, SecureRandom secureRandom) { - mRecoveryController = recoveryController; - mSecureRandom = secureRandom; - } - - /** - * Generates a new recoverable key using the {@link RecoveryController}. - * - * @throws InternalRecoveryServiceException if an unexpected error occurred generating the key. - * @throws LockScreenRequiredException if the user does not have a lock screen. A lock screen is - * required to generate a recoverable key. - */ - public RecoverableKeyStoreSecondaryKey generate() - throws InternalRecoveryServiceException, LockScreenRequiredException, - UnrecoverableKeyException { - String alias = generateId(); - mRecoveryController.generateKey(alias); - SecretKey key = (SecretKey) mRecoveryController.getKey(alias); - if (key == null) { - throw new InternalRecoveryServiceException( - String.format( - "Generated key %s but could not get it back immediately afterwards.", - alias)); - } - return new RecoverableKeyStoreSecondaryKey(alias, key); - } - - /** - * Removes the secondary key. This means the key will no longer be recoverable. - * - * @param alias The alias of the key. - * @throws InternalRecoveryServiceException if there was a {@link RecoveryController} error. - */ - public void remove(String alias) throws InternalRecoveryServiceException { - mRecoveryController.removeKey(alias); - } - - /** - * Returns the {@link RecoverableKeyStoreSecondaryKey} with {@code alias} if it is in the {@link - * RecoveryController}. Otherwise, {@link Optional#empty()}. - */ - public Optional<RecoverableKeyStoreSecondaryKey> get(String alias) - throws InternalRecoveryServiceException, UnrecoverableKeyException { - SecretKey secretKey = (SecretKey) mRecoveryController.getKey(alias); - return Optional.ofNullable(secretKey) - .map(key -> new RecoverableKeyStoreSecondaryKey(alias, key)); - } - - /** - * Generates a new key alias. This has more entropy than a UUID - it can be considered - * universally unique. - */ - private String generateId() { - byte[] id = new byte[BACKUP_KEY_SUFFIX_LENGTH_BITS / BITS_PER_BYTE]; - mSecureRandom.nextBytes(id); - return BACKUP_KEY_ALIAS_PREFIX + HexEncoding.encodeToString(id); - } - - /** Constructs a {@link RecoverableKeyStoreSecondaryKeyManager}. */ - public interface RecoverableKeyStoreSecondaryKeyManagerProvider { - /** Returns a newly constructed {@link RecoverableKeyStoreSecondaryKeyManager}. */ - RecoverableKeyStoreSecondaryKeyManager get(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RestoreKeyFetcher.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RestoreKeyFetcher.java deleted file mode 100644 index 6fb958bd1c1e..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RestoreKeyFetcher.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.keys; - -import android.security.keystore.recovery.InternalRecoveryServiceException; - -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager.RecoverableKeyStoreSecondaryKeyManagerProvider; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.util.Optional; - -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -/** Fetches the secondary key and uses it to unwrap the tertiary key during restore. */ -public class RestoreKeyFetcher { - - /** - * Retrieves the secondary key with the given alias and uses it to unwrap the given wrapped - * tertiary key. - * - * @param secondaryKeyManagerProvider Provider which creates {@link - * RecoverableKeyStoreSecondaryKeyManager} - * @param secondaryKeyAlias Alias of the secondary key used to wrap the tertiary key - * @param wrappedTertiaryKey Tertiary key wrapped with the secondary key above - * @return The unwrapped tertiary key - */ - public static SecretKey unwrapTertiaryKey( - RecoverableKeyStoreSecondaryKeyManagerProvider secondaryKeyManagerProvider, - String secondaryKeyAlias, - WrappedKeyProto.WrappedKey wrappedTertiaryKey) - throws KeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, - NoSuchPaddingException { - Optional<RecoverableKeyStoreSecondaryKey> secondaryKey = - getSecondaryKey(secondaryKeyManagerProvider, secondaryKeyAlias); - if (!secondaryKey.isPresent()) { - throw new KeyException("No key:" + secondaryKeyAlias); - } - - return KeyWrapUtils.unwrap(secondaryKey.get().getSecretKey(), wrappedTertiaryKey); - } - - private static Optional<RecoverableKeyStoreSecondaryKey> getSecondaryKey( - RecoverableKeyStoreSecondaryKeyManagerProvider secondaryKeyManagerProvider, - String secondaryKeyAlias) - throws KeyException { - try { - return secondaryKeyManagerProvider.get().get(secondaryKeyAlias); - } catch (InternalRecoveryServiceException | UnrecoverableKeyException e) { - throw new KeyException("Could not retrieve key:" + secondaryKeyAlias, e); - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java deleted file mode 100644 index 91b57cf69795..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationScheduler.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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.keys; - -import android.content.Context; -import android.util.Slog; - -import com.android.server.backup.encryption.CryptoSettings; -import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask; - -import java.io.File; -import java.time.Clock; -import java.util.Optional; - -/** - * Helps schedule rotations of secondary keys. - * - * <p>TODO(b/72028016) Replace with a job. - */ -public class SecondaryKeyRotationScheduler { - - private static final String TAG = "SecondaryKeyRotationScheduler"; - private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation"; - - private final Context mContext; - private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; - private final CryptoSettings mCryptoSettings; - private final Clock mClock; - - public SecondaryKeyRotationScheduler( - Context context, - RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager, - CryptoSettings cryptoSettings, - Clock clock) { - mContext = context; - mCryptoSettings = cryptoSettings; - mClock = clock; - mSecondaryKeyManager = secondaryKeyManager; - } - - /** - * Returns {@code true} if a sentinel file for forcing secondary key rotation is present. This - * is only for testing purposes. - */ - private boolean isForceRotationTestSentinelPresent() { - File file = new File(mContext.getFilesDir(), SENTINEL_FILE_PATH); - if (file.exists()) { - file.delete(); - return true; - } - return false; - } - - /** Start the key rotation task if it's time to do so */ - public void startRotationIfScheduled() { - if (isForceRotationTestSentinelPresent()) { - Slog.i(TAG, "Found force flag for secondary rotation. Starting now."); - startRotation(); - return; - } - - Optional<Long> maybeLastRotated = mCryptoSettings.getSecondaryLastRotated(); - if (!maybeLastRotated.isPresent()) { - Slog.v(TAG, "No previous rotation, scheduling from now."); - scheduleRotationFromNow(); - return; - } - - long lastRotated = maybeLastRotated.get(); - long now = mClock.millis(); - - if (lastRotated > now) { - Slog.i(TAG, "Last rotation was in the future. Clock must have changed. Rotate now."); - startRotation(); - return; - } - - long millisSinceLastRotation = now - lastRotated; - long rotationInterval = mCryptoSettings.backupSecondaryKeyRotationIntervalMs(); - if (millisSinceLastRotation >= rotationInterval) { - Slog.i( - TAG, - "Last rotation was more than " - + rotationInterval - + "ms (" - + millisSinceLastRotation - + "ms) in the past. Rotate now."); - startRotation(); - } - - Slog.v(TAG, "No rotation required, last " + lastRotated + "."); - } - - private void startRotation() { - scheduleRotationFromNow(); - new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager).run(); - } - - private void scheduleRotationFromNow() { - mCryptoSettings.setSecondaryLastRotated(mClock.millis()); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java deleted file mode 100644 index a425c720b9b8..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyGenerator.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (C) 2018 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.keys; - -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -/** 256-bit AES key generator. Each app should have its own separate AES key. */ -public class TertiaryKeyGenerator { - private static final int KEY_SIZE_BITS = 256; - private static final String KEY_ALGORITHM = "AES"; - - private final KeyGenerator mKeyGenerator; - - /** New instance generating keys using {@code secureRandom}. */ - public TertiaryKeyGenerator(SecureRandom secureRandom) { - try { - mKeyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); - mKeyGenerator.init(KEY_SIZE_BITS, secureRandom); - } catch (NoSuchAlgorithmException e) { - throw new AssertionError( - "Impossible condition: JCE thinks it does not support AES.", e); - } - } - - /** Generates a new random AES key. */ - public SecretKey generate() { - return mKeyGenerator.generateKey(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyManager.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyManager.java deleted file mode 100644 index a78357984912..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyManager.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.keys; - -import android.annotation.Nullable; -import android.content.Context; -import android.util.Slog; - -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.SecureRandom; -import java.util.Optional; - -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -/** - * Gets the correct tertiary key to use during a backup, rotating it if required. - * - * <p>Calling any method on this class will count a incremental backup against the app, and the key - * will be rotated if required. - */ -public class TertiaryKeyManager { - - private static final String TAG = "TertiaryKeyMgr"; - - private final TertiaryKeyStore mKeyStore; - private final TertiaryKeyGenerator mKeyGenerator; - private final TertiaryKeyRotationScheduler mTertiaryKeyRotationScheduler; - private final RecoverableKeyStoreSecondaryKey mSecondaryKey; - private final String mPackageName; - - private boolean mKeyRotated; - @Nullable private SecretKey mTertiaryKey; - - public TertiaryKeyManager( - Context context, - SecureRandom secureRandom, - TertiaryKeyRotationScheduler tertiaryKeyRotationScheduler, - RecoverableKeyStoreSecondaryKey secondaryKey, - String packageName) { - mSecondaryKey = secondaryKey; - mPackageName = packageName; - mKeyGenerator = new TertiaryKeyGenerator(secureRandom); - mKeyStore = TertiaryKeyStore.newInstance(context, secondaryKey); - mTertiaryKeyRotationScheduler = tertiaryKeyRotationScheduler; - } - - /** - * Returns either the previously used tertiary key, or a new tertiary key if there was no - * previous key or it needed to be rotated. - */ - public SecretKey getKey() - throws InvalidKeyException, IOException, IllegalBlockSizeException, - NoSuchPaddingException, NoSuchAlgorithmException, - InvalidAlgorithmParameterException { - init(); - return mTertiaryKey; - } - - /** Returns the key given by {@link #getKey()} wrapped by the secondary key. */ - public WrappedKeyProto.WrappedKey getWrappedKey() - throws InvalidKeyException, IOException, IllegalBlockSizeException, - NoSuchPaddingException, NoSuchAlgorithmException, - InvalidAlgorithmParameterException { - init(); - return KeyWrapUtils.wrap(mSecondaryKey.getSecretKey(), mTertiaryKey); - } - - /** - * Returns {@code true} if a new tertiary key was generated at the start of this session, - * otherwise {@code false}. - */ - public boolean wasKeyRotated() - throws InvalidKeyException, IllegalBlockSizeException, IOException, - NoSuchPaddingException, NoSuchAlgorithmException, - InvalidAlgorithmParameterException { - init(); - return mKeyRotated; - } - - private void init() - throws IllegalBlockSizeException, InvalidKeyException, IOException, - NoSuchAlgorithmException, NoSuchPaddingException, - InvalidAlgorithmParameterException { - if (mTertiaryKey != null) { - return; - } - - Optional<SecretKey> key = getExistingKeyIfNotRotated(); - - if (!key.isPresent()) { - Slog.d(TAG, "Generating new tertiary key for " + mPackageName); - - key = Optional.of(mKeyGenerator.generate()); - mKeyRotated = true; - mTertiaryKeyRotationScheduler.recordKeyRotation(mPackageName); - mKeyStore.save(mPackageName, key.get()); - } - - mTertiaryKey = key.get(); - - mTertiaryKeyRotationScheduler.recordBackup(mPackageName); - } - - private Optional<SecretKey> getExistingKeyIfNotRotated() - throws InvalidKeyException, IOException, InvalidAlgorithmParameterException, - NoSuchAlgorithmException, NoSuchPaddingException { - if (mTertiaryKeyRotationScheduler.isKeyRotationDue(mPackageName)) { - Slog.i(TAG, "Tertiary key rotation was required for " + mPackageName); - return Optional.empty(); - } else { - Slog.i(TAG, "Tertiary key rotation was not required"); - return mKeyStore.load(mPackageName); - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationScheduler.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationScheduler.java deleted file mode 100644 index f16a68d64213..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationScheduler.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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.keys; - -import android.content.Context; - -import com.android.internal.annotations.VisibleForTesting; - -/** - * Schedules tertiary key rotations in a staggered fashion. - * - * <p>Apps are due a key rotation after a certain number of backups. Rotations are then staggerered - * over a period of time, through restricting the number of rotations allowed in a 24-hour window. - * This will causes the apps to enter a staggered cycle of regular rotations. - * - * <p>Note: the methods in this class are not optimized to be super fast. They make blocking IO to - * ensure that scheduler information is committed to disk, so that it is available after the user - * turns their device off and on. This ought to be fine as - * - * <ul> - * <li>It will be invoked before a backup, so should never be invoked on the UI thread - * <li>It will be invoked before a backup, so the vast amount of time is spent on the backup, not - * writing tiny amounts of data to disk. - * </ul> - */ -public class TertiaryKeyRotationScheduler { - /** Default number of key rotations allowed within 24 hours. */ - private static final int KEY_ROTATION_LIMIT = 2; - - /** A new instance, using {@code context} to determine where to store state. */ - public static TertiaryKeyRotationScheduler getInstance(Context context) { - TertiaryKeyRotationWindowedCount windowedCount = - TertiaryKeyRotationWindowedCount.getInstance(context); - TertiaryKeyRotationTracker tracker = TertiaryKeyRotationTracker.getInstance(context); - return new TertiaryKeyRotationScheduler(tracker, windowedCount, KEY_ROTATION_LIMIT); - } - - private final TertiaryKeyRotationTracker mTracker; - private final TertiaryKeyRotationWindowedCount mWindowedCount; - private final int mMaximumRotationsPerWindow; - - /** - * A new instance. - * - * @param tracker Tracks how many times each application has backed up. - * @param windowedCount Tracks how many rotations have happened in the last 24 hours. - * @param maximumRotationsPerWindow The maximum number of key rotations allowed per 24 hours. - */ - @VisibleForTesting - TertiaryKeyRotationScheduler( - TertiaryKeyRotationTracker tracker, - TertiaryKeyRotationWindowedCount windowedCount, - int maximumRotationsPerWindow) { - mTracker = tracker; - mWindowedCount = windowedCount; - mMaximumRotationsPerWindow = maximumRotationsPerWindow; - } - - /** - * Returns {@code true} if the app with {@code packageName} is due having its key rotated. - * - * <p>This ought to be queried before backing up an app, to determine whether to do an - * incremental backup or a full backup. (A full backup forces key rotation.) - */ - public boolean isKeyRotationDue(String packageName) { - if (mWindowedCount.getCount() >= mMaximumRotationsPerWindow) { - return false; - } - return mTracker.isKeyRotationDue(packageName); - } - - /** - * Records that a backup happened for the app with the given {@code packageName}. - * - * <p>Each backup brings the app closer to the point at which a key rotation is due. - */ - public void recordBackup(String packageName) { - mTracker.recordBackup(packageName); - } - - /** - * Records a key rotation happened for the app with the given {@code packageName}. - * - * <p>This resets the countdown until the next key rotation is due. - */ - public void recordKeyRotation(String packageName) { - mTracker.resetCountdown(packageName); - mWindowedCount.record(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java deleted file mode 100644 index 1a281e79cc48..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTracker.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.keys; - -import static com.android.internal.util.Preconditions.checkArgument; - -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; - -import java.util.Locale; - -/** - * Tracks when a tertiary key rotation is due. - * - * <p>After a certain number of incremental backups, the device schedules a full backup, which will - * generate a new encryption key, effecting a key rotation. We should do this on a regular basis so - * that if a key does become compromised it has limited value to the attacker. - * - * <p>No additional synchronization of this class is provided. Only one instance should be used at - * any time. This should be fine as there should be no parallelism in backups. - */ -public class TertiaryKeyRotationTracker { - private static final int MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION = 31; - private static final String SHARED_PREFERENCES_NAME = "tertiary_key_rotation_tracker"; - - private static final String TAG = "TertiaryKeyRotationTracker"; - private static final boolean DEBUG = false; - - /** - * A new instance, using {@code context} to commit data to disk via {@link SharedPreferences}. - */ - public static TertiaryKeyRotationTracker getInstance(Context context) { - return new TertiaryKeyRotationTracker( - context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE), - MAX_BACKUPS_UNTIL_TERTIARY_KEY_ROTATION); - } - - private final SharedPreferences mSharedPreferences; - private final int mMaxBackupsTillRotation; - - /** - * New instance, storing data in {@code sharedPreferences} and initializing backup countdown to - * {@code maxBackupsTillRotation}. - */ - @VisibleForTesting - TertiaryKeyRotationTracker(SharedPreferences sharedPreferences, int maxBackupsTillRotation) { - checkArgument( - maxBackupsTillRotation >= 0, - String.format( - Locale.US, - "maxBackupsTillRotation should be non-negative but was %d", - maxBackupsTillRotation)); - mSharedPreferences = sharedPreferences; - mMaxBackupsTillRotation = maxBackupsTillRotation; - } - - /** - * Returns {@code true} if the given app is due having its key rotated. - * - * @param packageName The package name of the app. - */ - public boolean isKeyRotationDue(String packageName) { - return getBackupsSinceRotation(packageName) >= mMaxBackupsTillRotation; - } - - /** - * Records that an incremental backup has occurred. Each incremental backup brings the app - * closer to the time when its key should be rotated. - * - * @param packageName The package name of the app for which the backup occurred. - */ - public void recordBackup(String packageName) { - int backupsSinceRotation = getBackupsSinceRotation(packageName) + 1; - mSharedPreferences.edit().putInt(packageName, backupsSinceRotation).apply(); - if (DEBUG) { - Slog.d( - TAG, - String.format( - Locale.US, - "Incremental backup for %s. %d backups until key rotation.", - packageName, - Math.max( - 0, - mMaxBackupsTillRotation - - backupsSinceRotation))); - } - } - - /** - * Resets the rotation delay for the given app. Should be invoked after a key rotation. - * - * @param packageName Package name of the app whose key has rotated. - */ - public void resetCountdown(String packageName) { - mSharedPreferences.edit().putInt(packageName, 0).apply(); - } - - /** Marks all enrolled packages for key rotation. */ - public void markAllForRotation() { - SharedPreferences.Editor editor = mSharedPreferences.edit(); - for (String packageName : mSharedPreferences.getAll().keySet()) { - editor.putInt(packageName, mMaxBackupsTillRotation); - } - editor.apply(); - } - - private int getBackupsSinceRotation(String packageName) { - return mSharedPreferences.getInt(packageName, 0); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCount.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCount.java deleted file mode 100644 index b90343ad4b35..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCount.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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.keys; - -import android.content.Context; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.time.Clock; -import java.util.ArrayList; -import java.util.concurrent.TimeUnit; - -/** - * Tracks (and commits to disk) how many key rotations have happened in the last 24 hours. This - * allows us to limit (and therefore stagger) the number of key rotations in a given period of time. - * - * <p>Note to engineers thinking of replacing the below with fancier algorithms and data structures: - * we expect the total size of this count at any time to be below however many rotations we allow in - * the window, which is going to be in single digits. Any changes that mean we write to disk more - * frequently, that the code is no longer resistant to clock changes, or that the code is more - * difficult to understand are almost certainly not worthwhile. - */ -public class TertiaryKeyRotationWindowedCount { - private static final String TAG = "TertiaryKeyRotCount"; - - private static final int WINDOW_IN_HOURS = 24; - private static final String LOG_FILE_NAME = "tertiary_key_rotation_windowed_count"; - - private final Clock mClock; - private final File mFile; - private ArrayList<Long> mEvents; - - /** Returns a new instance, persisting state to the files dir of {@code context}. */ - public static TertiaryKeyRotationWindowedCount getInstance(Context context) { - File logFile = new File(context.getFilesDir(), LOG_FILE_NAME); - return new TertiaryKeyRotationWindowedCount(logFile, Clock.systemDefaultZone()); - } - - /** A new instance, committing state to {@code file}, and reading time from {@code clock}. */ - @VisibleForTesting - TertiaryKeyRotationWindowedCount(File file, Clock clock) { - mFile = file; - mClock = clock; - mEvents = new ArrayList<>(); - try { - loadFromFile(); - } catch (IOException e) { - Slog.e(TAG, "Error reading " + LOG_FILE_NAME, e); - } - } - - /** Records a key rotation at the current time. */ - public void record() { - mEvents.add(mClock.millis()); - compact(); - try { - saveToFile(); - } catch (IOException e) { - Slog.e(TAG, "Error saving " + LOG_FILE_NAME, e); - } - } - - /** Returns the number of key rotation that have been recorded in the window. */ - public int getCount() { - compact(); - return mEvents.size(); - } - - private void compact() { - long minimumTimestamp = getMinimumTimestamp(); - long now = mClock.millis(); - ArrayList<Long> compacted = new ArrayList<>(); - for (long event : mEvents) { - if (event >= minimumTimestamp && event <= now) { - compacted.add(event); - } - } - mEvents = compacted; - } - - private long getMinimumTimestamp() { - return mClock.millis() - TimeUnit.HOURS.toMillis(WINDOW_IN_HOURS) + 1; - } - - private void loadFromFile() throws IOException { - if (!mFile.exists()) { - return; - } - try (FileInputStream fis = new FileInputStream(mFile); - DataInputStream dis = new DataInputStream(fis)) { - while (true) { - mEvents.add(dis.readLong()); - } - } catch (EOFException eof) { - // expected - } - } - - private void saveToFile() throws IOException { - // File size is maximum number of key rotations in window multiplied by 8 bytes, which is - // why - // we just overwrite it each time. We expect it will always be less than 100 bytes in size. - try (FileOutputStream fos = new FileOutputStream(mFile); - DataOutputStream dos = new DataOutputStream(fos)) { - for (long event : mEvents) { - dos.writeLong(event); - } - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyStore.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyStore.java deleted file mode 100644 index 01444bf0cd00..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/TertiaryKeyStore.java +++ /dev/null @@ -1,202 +0,0 @@ -/* - * 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.keys; - -import static com.android.internal.util.Preconditions.checkArgument; - -import android.content.Context; -import android.util.ArrayMap; - -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; -import com.android.server.backup.encryption.storage.BackupEncryptionDb; -import com.android.server.backup.encryption.storage.TertiaryKey; -import com.android.server.backup.encryption.storage.TertiaryKeysTable; - -import com.google.protobuf.nano.CodedOutputByteBufferNano; - -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Map; -import java.util.Optional; - -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -/** - * Stores backup package keys. Each application package has its own {@link SecretKey}, which is used - * to encrypt the backup data. These keys are then wrapped by a master backup key, and stored in - * their wrapped form on disk and on the backup server. - * - * <p>For now this code only implements writing to disk. Once the backup server is ready, it will be - * extended to sync the keys there, also. - */ -public class TertiaryKeyStore { - - private final RecoverableKeyStoreSecondaryKey mSecondaryKey; - private final BackupEncryptionDb mDatabase; - - /** - * Creates an instance, using {@code secondaryKey} to wrap tertiary keys, and storing them in - * the database. - */ - public static TertiaryKeyStore newInstance( - Context context, RecoverableKeyStoreSecondaryKey secondaryKey) { - return new TertiaryKeyStore(secondaryKey, BackupEncryptionDb.newInstance(context)); - } - - private TertiaryKeyStore( - RecoverableKeyStoreSecondaryKey secondaryKey, BackupEncryptionDb database) { - mSecondaryKey = secondaryKey; - mDatabase = database; - } - - /** - * Saves the given key. - * - * @param applicationName The package name of the application for which this key will be used to - * encrypt data. e.g., "com.example.app". - * @param key The key. - * @throws InvalidKeyException if the backup key is not capable of wrapping. - * @throws IOException if there is an issue writing to the database. - */ - public void save(String applicationName, SecretKey key) - throws IOException, InvalidKeyException, IllegalBlockSizeException, - NoSuchPaddingException, NoSuchAlgorithmException { - checkApplicationName(applicationName); - - byte[] keyBytes = getEncodedKey(KeyWrapUtils.wrap(mSecondaryKey.getSecretKey(), key)); - - long pk; - try { - pk = - mDatabase - .getTertiaryKeysTable() - .addKey( - new TertiaryKey( - mSecondaryKey.getAlias(), applicationName, keyBytes)); - } finally { - mDatabase.close(); - } - - if (pk == -1) { - throw new IOException("Failed to commit to db"); - } - } - - /** - * Tries to load a key for the given application. - * - * @param applicationName The package name of the application, e.g. "com.example.app". - * @return The key if it is exists, {@link Optional#empty()} ()} otherwise. - * @throws InvalidKeyException if the backup key is not good for unwrapping. - * @throws IOException if there is a problem loading the key from the database. - */ - public Optional<SecretKey> load(String applicationName) - throws IOException, InvalidKeyException, InvalidAlgorithmParameterException, - NoSuchAlgorithmException, NoSuchPaddingException { - checkApplicationName(applicationName); - - Optional<TertiaryKey> keyFromDb; - try { - keyFromDb = - mDatabase - .getTertiaryKeysTable() - .getKey(mSecondaryKey.getAlias(), applicationName); - } finally { - mDatabase.close(); - } - - if (!keyFromDb.isPresent()) { - return Optional.empty(); - } - - WrappedKeyProto.WrappedKey wrappedKey = - WrappedKeyProto.WrappedKey.parseFrom(keyFromDb.get().getWrappedKeyBytes()); - return Optional.of(KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey)); - } - - /** - * Loads keys for all applications. - * - * @return All of the keys in a map keyed by package name. - * @throws IOException if there is an issue loading from the database. - * @throws InvalidKeyException if the backup key is not an appropriate key for unwrapping. - */ - public Map<String, SecretKey> getAll() - throws IOException, InvalidKeyException, InvalidAlgorithmParameterException, - NoSuchAlgorithmException, NoSuchPaddingException { - Map<String, TertiaryKey> tertiaries; - try { - tertiaries = mDatabase.getTertiaryKeysTable().getAllKeys(mSecondaryKey.getAlias()); - } finally { - mDatabase.close(); - } - - Map<String, SecretKey> unwrappedKeys = new ArrayMap<>(); - for (String applicationName : tertiaries.keySet()) { - WrappedKeyProto.WrappedKey wrappedKey = - WrappedKeyProto.WrappedKey.parseFrom( - tertiaries.get(applicationName).getWrappedKeyBytes()); - unwrappedKeys.put( - applicationName, KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey)); - } - - return unwrappedKeys; - } - - /** - * Adds all wrapped keys to the database. - * - * @throws IOException if an error occurred adding a wrapped key. - */ - public void putAll(Map<String, WrappedKeyProto.WrappedKey> wrappedKeysByApplicationName) - throws IOException { - TertiaryKeysTable tertiaryKeysTable = mDatabase.getTertiaryKeysTable(); - try { - - for (String applicationName : wrappedKeysByApplicationName.keySet()) { - byte[] keyBytes = getEncodedKey(wrappedKeysByApplicationName.get(applicationName)); - long primaryKey = - tertiaryKeysTable.addKey( - new TertiaryKey( - mSecondaryKey.getAlias(), applicationName, keyBytes)); - - if (primaryKey == -1) { - throw new IOException("Failed to commit to db"); - } - } - - } finally { - mDatabase.close(); - } - } - - private static void checkApplicationName(String applicationName) { - checkArgument(!applicationName.isEmpty(), "applicationName must not be empty string."); - checkArgument(!applicationName.contains("/"), "applicationName must not contain slash."); - } - - private byte[] getEncodedKey(WrappedKeyProto.WrappedKey key) throws IOException { - byte[] buffer = new byte[key.getSerializedSize()]; - CodedOutputByteBufferNano out = CodedOutputByteBufferNano.newInstance(buffer); - key.writeTo(out); - return buffer; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java deleted file mode 100644 index 56e1c053d8e3..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.kv; - -import static com.android.internal.util.Preconditions.checkState; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.ChunkHasher; -import com.android.server.backup.encryption.protos.nano.KeyValuePairProto; -import com.android.server.backup.encryption.tasks.DecryptedChunkOutput; - -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Builds a key value backup set from plaintext chunks. Computes a digest over the sorted SHA-256 - * hashes of the chunks. - */ -public class DecryptedChunkKvOutput implements DecryptedChunkOutput { - @VisibleForTesting static final String DIGEST_ALGORITHM = "SHA-256"; - - private final ChunkHasher mChunkHasher; - private final List<KeyValuePairProto.KeyValuePair> mUnsortedPairs = new ArrayList<>(); - private final List<ChunkHash> mUnsortedHashes = new ArrayList<>(); - private boolean mClosed; - - /** Constructs a new instance which computers the digest using the given hasher. */ - public DecryptedChunkKvOutput(ChunkHasher chunkHasher) { - mChunkHasher = chunkHasher; - } - - @Override - public DecryptedChunkOutput open() { - // As we don't have any resources there is nothing to open. - return this; - } - - @Override - public void processChunk(byte[] plaintextBuffer, int length) - throws IOException, InvalidKeyException { - checkState(!mClosed, "Cannot process chunk after close()"); - KeyValuePairProto.KeyValuePair kvPair = new KeyValuePairProto.KeyValuePair(); - KeyValuePairProto.KeyValuePair.mergeFrom(kvPair, plaintextBuffer, 0, length); - mUnsortedPairs.add(kvPair); - // TODO(b/71492289): Update ChunkHasher to accept offset and length so we don't have to copy - // the buffer into a smaller array. - mUnsortedHashes.add(mChunkHasher.computeHash(Arrays.copyOf(plaintextBuffer, length))); - } - - @Override - public void close() { - // As we don't have any resources there is nothing to close. - mClosed = true; - } - - @Override - public byte[] getDigest() throws NoSuchAlgorithmException { - checkState(mClosed, "Must close() before getDigest()"); - MessageDigest digest = getMessageDigest(); - Collections.sort(mUnsortedHashes); - for (ChunkHash hash : mUnsortedHashes) { - digest.update(hash.getHash()); - } - return digest.digest(); - } - - private static MessageDigest getMessageDigest() throws NoSuchAlgorithmException { - return MessageDigest.getInstance(DIGEST_ALGORITHM); - } - - /** - * Returns the key value pairs from the backup, sorted lexicographically by key. - * - * <p>You must call {@link #close} first. - */ - public List<KeyValuePairProto.KeyValuePair> getPairs() { - checkState(mClosed, "Must close() before getPairs()"); - Collections.sort( - mUnsortedPairs, - new Comparator<KeyValuePairProto.KeyValuePair>() { - @Override - public int compare( - KeyValuePairProto.KeyValuePair o1, KeyValuePairProto.KeyValuePair o2) { - return o1.key.compareTo(o2.key); - } - }); - return mUnsortedPairs; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java deleted file mode 100644 index 217304c535ca..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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.kv; - -import static com.android.internal.util.Preconditions.checkArgument; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.protos.nano.KeyValueListingProto; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; - -/** - * Builds a {@link KeyValueListingProto.KeyValueListing}, which is a nano proto and so has no - * builder. - */ -public class KeyValueListingBuilder { - private final List<KeyValueListingProto.KeyValueEntry> mEntries = new ArrayList<>(); - - /** Adds a new pair entry to the listing. */ - public KeyValueListingBuilder addPair(String key, ChunkHash hash) { - checkArgument(key.length() != 0, "Key must have non-zero length"); - Objects.requireNonNull(hash, "Hash must not be null"); - - KeyValueListingProto.KeyValueEntry entry = new KeyValueListingProto.KeyValueEntry(); - entry.key = key; - entry.hash = hash.getHash(); - mEntries.add(entry); - - return this; - } - - /** Adds all pairs contained in a map, where the map is from key to hash. */ - public KeyValueListingBuilder addAll(Map<String, ChunkHash> map) { - for (Entry<String, ChunkHash> entry : map.entrySet()) { - addPair(entry.getKey(), entry.getValue()); - } - - return this; - } - - /** Returns a new listing containing all the pairs added so far. */ - public KeyValueListingProto.KeyValueListing build() { - if (mEntries.size() == 0) { - return emptyListing(); - } - - KeyValueListingProto.KeyValueListing listing = new KeyValueListingProto.KeyValueListing(); - listing.entries = new KeyValueListingProto.KeyValueEntry[mEntries.size()]; - mEntries.toArray(listing.entries); - return listing; - } - - /** Returns a new listing which does not contain any pairs. */ - public static KeyValueListingProto.KeyValueListing emptyListing() { - KeyValueListingProto.KeyValueListing listing = new KeyValueListingProto.KeyValueListing(); - listing.entries = KeyValueListingProto.KeyValueEntry.emptyArray(); - return listing; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDb.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDb.java deleted file mode 100644 index 9f6c03a6f393..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDb.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2018 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.storage; - -import android.content.Context; - -/** - * Backup encryption SQLite database. All instances are threadsafe. - * - * <p>The database is automatically opened when accessing one of the tables. After the caller is - * done they must call {@link #close()}. - */ -public class BackupEncryptionDb { - private final BackupEncryptionDbHelper mHelper; - - /** A new instance, using the storage defined by {@code context}. */ - public static BackupEncryptionDb newInstance(Context context) { - BackupEncryptionDbHelper helper = new BackupEncryptionDbHelper(context); - helper.setWriteAheadLoggingEnabled(true); - return new BackupEncryptionDb(helper); - } - - private BackupEncryptionDb(BackupEncryptionDbHelper helper) { - mHelper = helper; - } - - public TertiaryKeysTable getTertiaryKeysTable() { - return new TertiaryKeysTable(mHelper); - } - - /** Deletes the database. */ - public void clear() throws EncryptionDbException { - mHelper.resetDatabase(); - } - - /** - * Closes the database if it is open. - * - * <p>After calling this, the caller may access one of the tables again which will automatically - * reopen the database. - */ - public void close() { - mHelper.close(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java deleted file mode 100644 index 5e8a8d9fc2ae..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbContract.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2018 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.storage; - -import android.provider.BaseColumns; - -/** Contract for the backup encryption database. Describes tables present. */ -class BackupEncryptionDbContract { - /** - * Table containing tertiary keys belonging to the user. Tertiary keys are wrapped by a - * secondary key, which never leaves {@code AndroidKeyStore} (a provider for {@link - * java.security.KeyStore}). Each application has a tertiary key, which is used to encrypt the - * backup data. - */ - static class TertiaryKeysEntry implements BaseColumns { - static final String TABLE_NAME = "tertiary_keys"; - - /** Alias of the secondary key used to wrap the tertiary key. */ - static final String COLUMN_NAME_SECONDARY_KEY_ALIAS = "secondary_key_alias"; - - /** Name of the package to which the tertiary key belongs. */ - static final String COLUMN_NAME_PACKAGE_NAME = "package_name"; - - /** Encrypted bytes of the tertiary key. */ - static final String COLUMN_NAME_WRAPPED_KEY_BYTES = "wrapped_key_bytes"; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java deleted file mode 100644 index c70634248dca..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/BackupEncryptionDbHelper.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2018 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.storage; - -import static com.android.server.backup.encryption.storage.BackupEncryptionDbContract.TertiaryKeysEntry; - -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.database.sqlite.SQLiteOpenHelper; - -/** Helper for creating an instance of the backup encryption database. */ -class BackupEncryptionDbHelper extends SQLiteOpenHelper { - private static final int DATABASE_VERSION = 1; - static final String DATABASE_NAME = "backupencryption.db"; - - private static final String SQL_CREATE_TERTIARY_KEYS_ENTRY = - "CREATE TABLE " - + TertiaryKeysEntry.TABLE_NAME - + " ( " - + TertiaryKeysEntry._ID - + " INTEGER PRIMARY KEY," - + TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS - + " TEXT," - + TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME - + " TEXT," - + TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES - + " BLOB," - + "UNIQUE(" - + TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS - + "," - + TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME - + "))"; - - private static final String SQL_DROP_TERTIARY_KEYS_ENTRY = - "DROP TABLE IF EXISTS " + TertiaryKeysEntry.TABLE_NAME; - - BackupEncryptionDbHelper(Context context) { - super(context, DATABASE_NAME, /*factory=*/ null, DATABASE_VERSION); - } - - public void resetDatabase() throws EncryptionDbException { - SQLiteDatabase db = getWritableDatabaseSafe(); - db.execSQL(SQL_DROP_TERTIARY_KEYS_ENTRY); - onCreate(db); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(SQL_CREATE_TERTIARY_KEYS_ENTRY); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL(SQL_DROP_TERTIARY_KEYS_ENTRY); - onCreate(db); - } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.execSQL(SQL_DROP_TERTIARY_KEYS_ENTRY); - onCreate(db); - } - - /** - * Calls {@link #getWritableDatabase()}, but catches the unchecked {@link SQLiteException} and - * rethrows {@link EncryptionDbException}. - */ - public SQLiteDatabase getWritableDatabaseSafe() throws EncryptionDbException { - try { - return super.getWritableDatabase(); - } catch (SQLiteException e) { - throw new EncryptionDbException(e); - } - } - - /** - * Calls {@link #getReadableDatabase()}, but catches the unchecked {@link SQLiteException} and - * rethrows {@link EncryptionDbException}. - */ - public SQLiteDatabase getReadableDatabaseSafe() throws EncryptionDbException { - try { - return super.getReadableDatabase(); - } catch (SQLiteException e) { - throw new EncryptionDbException(e); - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/EncryptionDbException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/EncryptionDbException.java deleted file mode 100644 index 82f7dead1b50..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/EncryptionDbException.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2018 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.storage; - -import java.io.IOException; - -/** Thrown when there is a problem reading or writing the encryption database. */ -public class EncryptionDbException extends IOException { - public EncryptionDbException(Throwable cause) { - super(cause); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKey.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKey.java deleted file mode 100644 index 39a2c6ebb9c3..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKey.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2018 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.storage; - -/** Wrapped bytes of a tertiary key. */ -public class TertiaryKey { - private final String mSecondaryKeyAlias; - private final String mPackageName; - private final byte[] mWrappedKeyBytes; - - /** - * Creates a new instance. - * - * @param secondaryKeyAlias Alias of the secondary used to wrap the key. - * @param packageName The package name of the app to which the key belongs. - * @param wrappedKeyBytes The wrapped key bytes. - */ - public TertiaryKey(String secondaryKeyAlias, String packageName, byte[] wrappedKeyBytes) { - mSecondaryKeyAlias = secondaryKeyAlias; - mPackageName = packageName; - mWrappedKeyBytes = wrappedKeyBytes; - } - - /** Returns the alias of the secondary key used to wrap this tertiary key. */ - public String getSecondaryKeyAlias() { - return mSecondaryKeyAlias; - } - - /** Returns the package name of the application this key relates to. */ - public String getPackageName() { - return mPackageName; - } - - /** Returns the wrapped bytes of the key. */ - public byte[] getWrappedKeyBytes() { - return mWrappedKeyBytes; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKeysTable.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKeysTable.java deleted file mode 100644 index d8d40c402a84..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/storage/TertiaryKeysTable.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2018 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.storage; - -import static com.android.server.backup.encryption.storage.BackupEncryptionDbContract.TertiaryKeysEntry; - -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.util.ArrayMap; - -import java.util.Collections; -import java.util.Map; -import java.util.Optional; - -/** Database table for storing and retrieving tertiary keys. */ -public class TertiaryKeysTable { - private final BackupEncryptionDbHelper mHelper; - - TertiaryKeysTable(BackupEncryptionDbHelper helper) { - mHelper = helper; - } - - /** - * Adds the {@code tertiaryKey} to the database. - * - * @return The primary key of the inserted row if successful, -1 otherwise. - */ - public long addKey(TertiaryKey tertiaryKey) throws EncryptionDbException { - SQLiteDatabase db = mHelper.getWritableDatabaseSafe(); - ContentValues values = new ContentValues(); - values.put( - TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS, - tertiaryKey.getSecondaryKeyAlias()); - values.put(TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME, tertiaryKey.getPackageName()); - values.put( - TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES, tertiaryKey.getWrappedKeyBytes()); - return db.replace(TertiaryKeysEntry.TABLE_NAME, /*nullColumnHack=*/ null, values); - } - - /** Gets the key wrapped by {@code secondaryKeyAlias} for app with {@code packageName}. */ - public Optional<TertiaryKey> getKey(String secondaryKeyAlias, String packageName) - throws EncryptionDbException { - SQLiteDatabase db = mHelper.getReadableDatabaseSafe(); - String[] projection = { - TertiaryKeysEntry._ID, - TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS, - TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME, - TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES - }; - String selection = - TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS - + " = ? AND " - + TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME - + " = ?"; - String[] selectionArguments = {secondaryKeyAlias, packageName}; - - try (Cursor cursor = - db.query( - TertiaryKeysEntry.TABLE_NAME, - projection, - selection, - selectionArguments, - /*groupBy=*/ null, - /*having=*/ null, - /*orderBy=*/ null)) { - int count = cursor.getCount(); - if (count == 0) { - return Optional.empty(); - } - - cursor.moveToFirst(); - byte[] wrappedKeyBytes = - cursor.getBlob( - cursor.getColumnIndexOrThrow( - TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES)); - return Optional.of(new TertiaryKey(secondaryKeyAlias, packageName, wrappedKeyBytes)); - } - } - - /** Returns all keys wrapped with {@code tertiaryKeyAlias} as an unmodifiable map. */ - public Map<String, TertiaryKey> getAllKeys(String secondaryKeyAlias) - throws EncryptionDbException { - SQLiteDatabase db = mHelper.getReadableDatabaseSafe(); - String[] projection = { - TertiaryKeysEntry._ID, - TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS, - TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME, - TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES - }; - String selection = TertiaryKeysEntry.COLUMN_NAME_SECONDARY_KEY_ALIAS + " = ?"; - String[] selectionArguments = {secondaryKeyAlias}; - - Map<String, TertiaryKey> keysByPackageName = new ArrayMap<>(); - try (Cursor cursor = - db.query( - TertiaryKeysEntry.TABLE_NAME, - projection, - selection, - selectionArguments, - /*groupBy=*/ null, - /*having=*/ null, - /*orderBy=*/ null)) { - while (cursor.moveToNext()) { - String packageName = - cursor.getString( - cursor.getColumnIndexOrThrow( - TertiaryKeysEntry.COLUMN_NAME_PACKAGE_NAME)); - byte[] wrappedKeyBytes = - cursor.getBlob( - cursor.getColumnIndexOrThrow( - TertiaryKeysEntry.COLUMN_NAME_WRAPPED_KEY_BYTES)); - keysByPackageName.put( - packageName, - new TertiaryKey(secondaryKeyAlias, packageName, wrappedKeyBytes)); - } - } - return Collections.unmodifiableMap(keysByPackageName); - } -} 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 deleted file mode 100644 index 2e8a61f05970..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ActiveSecondaryNotInKeychainException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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/BackupEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupEncrypter.java deleted file mode 100644 index 95d0d97b4073..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupEncrypter.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 java.util.Collections.unmodifiableList; - -import android.annotation.Nullable; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.EncryptedChunk; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import javax.crypto.SecretKey; - -/** Task which reads data from some source, splits it into chunks and encrypts new chunks. */ -public interface BackupEncrypter { - /** The algorithm which we use to compute the digest of the backup file plaintext. */ - String MESSAGE_DIGEST_ALGORITHM = "SHA-256"; - - /** - * Splits the backup input into encrypted chunks and encrypts new chunks. - * - * @param secretKey Key used to encrypt backup. - * @param fingerprintMixerSalt Fingerprint mixer salt used for content-defined chunking during a - * full backup. Should be {@code null} for a key-value backup. - * @param existingChunks Set of the SHA-256 Macs of chunks the server already has. - * @return a result containing an array of new encrypted chunks to upload, and an ordered - * listing of the chunks in the backup file. - * @throws IOException if a problem occurs reading from the backup data. - * @throws GeneralSecurityException if there is a problem encrypting the data. - */ - Result backup( - SecretKey secretKey, - @Nullable byte[] fingerprintMixerSalt, - Set<ChunkHash> existingChunks) - throws IOException, GeneralSecurityException; - - /** - * The result of an incremental backup. Contains new encrypted chunks to upload, and an ordered - * list of the chunks in the backup file. - */ - class Result { - private final List<ChunkHash> mAllChunks; - private final List<EncryptedChunk> mNewChunks; - private final byte[] mDigest; - - public Result(List<ChunkHash> allChunks, List<EncryptedChunk> newChunks, byte[] digest) { - mAllChunks = unmodifiableList(new ArrayList<>(allChunks)); - mDigest = digest; - mNewChunks = unmodifiableList(new ArrayList<>(newChunks)); - } - - /** - * Returns an unmodifiable list of the hashes of all the chunks in the backup, in the order - * they appear in the plaintext. - */ - public List<ChunkHash> getAllChunks() { - return mAllChunks; - } - - /** Returns an unmodifiable list of the new chunks in the backup. */ - public List<EncryptedChunk> getNewChunks() { - return mNewChunks; - } - - /** Returns the message digest of the backup. */ - public byte[] getDigest() { - return mDigest; - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTask.java deleted file mode 100644 index 9bf148ddc901..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTask.java +++ /dev/null @@ -1,378 +0,0 @@ -/* - * 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.util.Slog; -import android.util.SparseIntArray; - -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkOrdering; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunksMetadata; - -import com.google.protobuf.nano.InvalidProtocolBufferNanoException; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Locale; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.GCMParameterSpec; - -/** - * A backup file consists of, in order: - * - * <ul> - * <li>A randomly ordered sequence of encrypted chunks - * <li>A plaintext {@link ChunksMetadata} proto, containing the bytes of an encrypted {@link - * ChunkOrdering} proto. - * <li>A 64-bit long denoting the offset of the file at which the ChunkOrdering proto starts. - * </ul> - * - * <p>This task decrypts such a blob and writes the plaintext to another file. - * - * <p>The backup file has two formats to indicate the boundaries of the chunks in the encrypted - * file. In {@link ChunksMetadataProto#EXPLICIT_STARTS} mode the chunk ordering contains the start - * positions of each chunk and the decryptor outputs the chunks in the order they appeared in the - * plaintext file. In {@link ChunksMetadataProto#INLINE_LENGTHS} mode the length of each encrypted - * chunk is prepended to the chunk in the file and the decryptor outputs the chunks in no specific - * order. - * - * <p>{@link ChunksMetadataProto#EXPLICIT_STARTS} is for use with full backup (Currently used for - * all backups as b/77188289 is not implemented yet), {@link ChunksMetadataProto#INLINE_LENGTHS} - * will be used for kv backup (once b/77188289 is implemented) to avoid re-uploading the chunk - * ordering (see b/70782620). - */ -public class BackupFileDecryptorTask { - private static final String TAG = "BackupFileDecryptorTask"; - - private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; - private static final int GCM_NONCE_LENGTH_BYTES = 12; - private static final int GCM_TAG_LENGTH_BYTES = 16; - private static final int BITS_PER_BYTE = 8; - private static final String READ_MODE = "r"; - private static final int BYTES_PER_LONG = 64 / BITS_PER_BYTE; - - private final Cipher mCipher; - private final SecretKey mSecretKey; - - /** - * A new instance. - * - * @param secretKey The tertiary key used to encrypt the backup blob. - */ - public BackupFileDecryptorTask(SecretKey secretKey) - throws NoSuchPaddingException, NoSuchAlgorithmException { - this.mCipher = Cipher.getInstance(CIPHER_ALGORITHM); - this.mSecretKey = secretKey; - } - - /** - * Runs the task, reading the encrypted data from {@code input} and writing the plaintext data - * to {@code output}. - * - * @param inputFile The encrypted backup file. - * @param decryptedChunkOutput Unopened output to write the plaintext to, which this class will - * open and close during decryption. - * @throws IOException if an error occurred reading the encrypted file or writing the plaintext, - * or if one of the protos could not be deserialized. - */ - public void decryptFile(File inputFile, DecryptedChunkOutput decryptedChunkOutput) - throws IOException, EncryptedRestoreException, IllegalBlockSizeException, - BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, - ShortBufferException, NoSuchAlgorithmException { - RandomAccessFile input = new RandomAccessFile(inputFile, READ_MODE); - - long metadataOffset = getChunksMetadataOffset(input); - ChunksMetadataProto.ChunksMetadata chunksMetadata = - getChunksMetadata(input, metadataOffset); - ChunkOrdering chunkOrdering = decryptChunkOrdering(chunksMetadata); - - if (chunksMetadata.chunkOrderingType == ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED - || chunksMetadata.chunkOrderingType == ChunksMetadataProto.EXPLICIT_STARTS) { - Slog.d(TAG, "Using explicit starts"); - decryptFileWithExplicitStarts( - input, decryptedChunkOutput, chunkOrdering, metadataOffset); - - } else if (chunksMetadata.chunkOrderingType == ChunksMetadataProto.INLINE_LENGTHS) { - Slog.d(TAG, "Using inline lengths"); - decryptFileWithInlineLengths(input, decryptedChunkOutput, metadataOffset); - - } else { - throw new UnsupportedEncryptedFileException( - "Unknown chunk ordering type:" + chunksMetadata.chunkOrderingType); - } - - if (!Arrays.equals(decryptedChunkOutput.getDigest(), chunkOrdering.checksum)) { - throw new MessageDigestMismatchException("Checksums did not match"); - } - } - - private void decryptFileWithExplicitStarts( - RandomAccessFile input, - DecryptedChunkOutput decryptedChunkOutput, - ChunkOrdering chunkOrdering, - long metadataOffset) - throws IOException, InvalidKeyException, IllegalBlockSizeException, - InvalidAlgorithmParameterException, ShortBufferException, BadPaddingException, - NoSuchAlgorithmException { - SparseIntArray chunkLengthsByPosition = - getChunkLengths(chunkOrdering.starts, (int) metadataOffset); - int largestChunkLength = getLargestChunkLength(chunkLengthsByPosition); - byte[] encryptedChunkBuffer = new byte[largestChunkLength]; - // largestChunkLength is 0 if the backup file contains zero chunks e.g. 0 kv pairs. - int plaintextBufferLength = - Math.max(0, largestChunkLength - GCM_NONCE_LENGTH_BYTES - GCM_TAG_LENGTH_BYTES); - byte[] plaintextChunkBuffer = new byte[plaintextBufferLength]; - - try (DecryptedChunkOutput output = decryptedChunkOutput.open()) { - for (int start : chunkOrdering.starts) { - int length = chunkLengthsByPosition.get(start); - - input.seek(start); - input.readFully(encryptedChunkBuffer, 0, length); - int plaintextLength = - decryptChunk(encryptedChunkBuffer, length, plaintextChunkBuffer); - outputChunk(output, plaintextChunkBuffer, plaintextLength); - } - } - } - - private void decryptFileWithInlineLengths( - RandomAccessFile input, DecryptedChunkOutput decryptedChunkOutput, long metadataOffset) - throws MalformedEncryptedFileException, IOException, IllegalBlockSizeException, - BadPaddingException, InvalidAlgorithmParameterException, ShortBufferException, - InvalidKeyException, NoSuchAlgorithmException { - input.seek(0); - try (DecryptedChunkOutput output = decryptedChunkOutput.open()) { - while (input.getFilePointer() < metadataOffset) { - long start = input.getFilePointer(); - int encryptedChunkLength = input.readInt(); - - if (encryptedChunkLength <= 0) { - // If the length of the encrypted chunk is not positive we will not make - // progress reading the file and so will loop forever. - throw new MalformedEncryptedFileException( - "Encrypted chunk length not positive:" + encryptedChunkLength); - } - - if (start + encryptedChunkLength > metadataOffset) { - throw new MalformedEncryptedFileException( - String.format( - Locale.US, - "Encrypted chunk longer (%d) than file (%d)", - encryptedChunkLength, - metadataOffset)); - } - - byte[] plaintextChunk = new byte[encryptedChunkLength]; - byte[] plaintext = - new byte - [encryptedChunkLength - - GCM_NONCE_LENGTH_BYTES - - GCM_TAG_LENGTH_BYTES]; - - input.readFully(plaintextChunk); - - int plaintextChunkLength = - decryptChunk(plaintextChunk, encryptedChunkLength, plaintext); - outputChunk(output, plaintext, plaintextChunkLength); - } - } - } - - private void outputChunk( - DecryptedChunkOutput output, byte[] plaintextChunkBuffer, int plaintextLength) - throws IOException, InvalidKeyException, NoSuchAlgorithmException { - output.processChunk(plaintextChunkBuffer, plaintextLength); - } - - /** - * Decrypts chunk and returns the length of the plaintext. - * - * @param encryptedChunkBuffer The encrypted data, prefixed by the nonce. - * @param encryptedChunkBufferLength The length of the encrypted chunk (including nonce). - * @param plaintextChunkBuffer The buffer into which to write the plaintext chunk. - * @return The length of the plaintext chunk. - */ - private int decryptChunk( - byte[] encryptedChunkBuffer, - int encryptedChunkBufferLength, - byte[] plaintextChunkBuffer) - throws InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, - ShortBufferException, IllegalBlockSizeException { - - mCipher.init( - Cipher.DECRYPT_MODE, - mSecretKey, - new GCMParameterSpec( - GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE, - encryptedChunkBuffer, - 0, - GCM_NONCE_LENGTH_BYTES)); - - return mCipher.doFinal( - encryptedChunkBuffer, - GCM_NONCE_LENGTH_BYTES, - encryptedChunkBufferLength - GCM_NONCE_LENGTH_BYTES, - plaintextChunkBuffer); - } - - /** Given all the lengths, returns the largest length. */ - private int getLargestChunkLength(SparseIntArray lengths) { - int maxSeen = 0; - for (int i = 0; i < lengths.size(); i++) { - maxSeen = Math.max(maxSeen, lengths.valueAt(i)); - } - return maxSeen; - } - - /** - * From a list of the starting position of each chunk in the correct order of the backup data, - * calculates a mapping from start position to length of that chunk. - * - * @param starts The start positions of chunks, in order. - * @param chunkOrderingPosition Where the {@link ChunkOrdering} proto starts, used to calculate - * the length of the last chunk. - * @return The mapping. - */ - private SparseIntArray getChunkLengths(int[] starts, int chunkOrderingPosition) { - int[] boundaries = Arrays.copyOf(starts, starts.length + 1); - boundaries[boundaries.length - 1] = chunkOrderingPosition; - Arrays.sort(boundaries); - - SparseIntArray lengths = new SparseIntArray(); - for (int i = 0; i < boundaries.length - 1; i++) { - lengths.put(boundaries[i], boundaries[i + 1] - boundaries[i]); - } - return lengths; - } - - /** - * Reads and decrypts the {@link ChunkOrdering} from the {@link ChunksMetadata}. - * - * @param metadata The metadata. - * @return The ordering. - * @throws InvalidProtocolBufferNanoException if there is an issue deserializing the proto. - */ - private ChunkOrdering decryptChunkOrdering(ChunksMetadata metadata) - throws InvalidProtocolBufferNanoException, InvalidAlgorithmParameterException, - InvalidKeyException, BadPaddingException, IllegalBlockSizeException, - UnsupportedEncryptedFileException { - assertCryptoSupported(metadata); - - mCipher.init( - Cipher.DECRYPT_MODE, - mSecretKey, - new GCMParameterSpec( - GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE, - metadata.chunkOrdering, - 0, - GCM_NONCE_LENGTH_BYTES)); - - byte[] decrypted = - mCipher.doFinal( - metadata.chunkOrdering, - GCM_NONCE_LENGTH_BYTES, - metadata.chunkOrdering.length - GCM_NONCE_LENGTH_BYTES); - - return ChunkOrdering.parseFrom(decrypted); - } - - /** - * Asserts that the Cipher and MessageDigest algorithms in the backup metadata are supported. - * For now we only support SHA-256 for checksum and 256-bit AES/GCM/NoPadding for the Cipher. - * - * @param chunksMetadata The file metadata. - * @throws UnsupportedEncryptedFileException if any algorithm is unsupported. - */ - private void assertCryptoSupported(ChunksMetadata chunksMetadata) - throws UnsupportedEncryptedFileException { - if (chunksMetadata.checksumType != ChunksMetadataProto.SHA_256) { - // For now we only support SHA-256. - throw new UnsupportedEncryptedFileException( - "Unrecognized checksum type for backup (this version of backup only supports" - + " SHA-256): " - + chunksMetadata.checksumType); - } - - if (chunksMetadata.cipherType != ChunksMetadataProto.AES_256_GCM) { - throw new UnsupportedEncryptedFileException( - "Unrecognized cipher type for backup (this version of backup only supports" - + " AES-256-GCM: " - + chunksMetadata.cipherType); - } - } - - /** - * Reads the offset of the {@link ChunksMetadata} proto from the end of the file. - * - * @return The offset. - * @throws IOException if there is an error reading. - */ - private long getChunksMetadataOffset(RandomAccessFile input) throws IOException { - input.seek(input.length() - BYTES_PER_LONG); - return input.readLong(); - } - - /** - * Reads the {@link ChunksMetadata} proto from the given position in the file. - * - * @param input The encrypted file. - * @param position The position where the proto starts. - * @return The proto. - * @throws IOException if there is an issue reading the file or deserializing the proto. - */ - private ChunksMetadata getChunksMetadata(RandomAccessFile input, long position) - throws IOException, MalformedEncryptedFileException { - long length = input.length(); - if (position >= length || position < 0) { - throw new MalformedEncryptedFileException( - String.format( - Locale.US, - "%d is not valid position for chunks metadata in file of %d bytes", - position, - length)); - } - - // Read chunk ordering bytes - input.seek(position); - long chunksMetadataLength = input.length() - BYTES_PER_LONG - position; - byte[] chunksMetadataBytes = new byte[(int) chunksMetadataLength]; - input.readFully(chunksMetadataBytes); - - try { - return ChunksMetadata.parseFrom(chunksMetadataBytes); - } catch (InvalidProtocolBufferNanoException e) { - throw new MalformedEncryptedFileException( - String.format( - Locale.US, - "Could not read chunks metadata at position %d of file of %d bytes", - position, - length)); - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java deleted file mode 100644 index 45798d32885a..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypter.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.util.Slog; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.ChunkEncryptor; -import com.android.server.backup.encryption.chunking.ChunkHasher; -import com.android.server.backup.encryption.chunking.EncryptedChunk; -import com.android.server.backup.encryption.chunking.cdc.ContentDefinedChunker; -import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer; -import com.android.server.backup.encryption.chunking.cdc.IsChunkBreakpoint; -import com.android.server.backup.encryption.chunking.cdc.RabinFingerprint64; - -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.crypto.SecretKey; - -/** - * Splits backup data into variable-sized chunks using content-defined chunking, then encrypts the - * chunks. Given a hash of the SHA-256s of existing chunks, performs an incremental backup (i.e., - * only encrypts new chunks). - */ -public class BackupStreamEncrypter implements BackupEncrypter { - private static final String TAG = "BackupStreamEncryptor"; - - private final InputStream mData; - private final int mMinChunkSizeBytes; - private final int mMaxChunkSizeBytes; - private final int mAverageChunkSizeBytes; - - /** - * A new instance over the given distribution of chunk sizes. - * - * @param data The data to be backed up. - * @param minChunkSizeBytes The minimum chunk size. No chunk will be smaller than this. - * @param maxChunkSizeBytes The maximum chunk size. No chunk will be larger than this. - * @param averageChunkSizeBytes The average chunk size. The mean size of chunks will be roughly - * this (with a few tens of bytes of overhead for the initialization vector and message - * authentication code). - */ - public BackupStreamEncrypter( - InputStream data, - int minChunkSizeBytes, - int maxChunkSizeBytes, - int averageChunkSizeBytes) { - this.mData = data; - this.mMinChunkSizeBytes = minChunkSizeBytes; - this.mMaxChunkSizeBytes = maxChunkSizeBytes; - this.mAverageChunkSizeBytes = averageChunkSizeBytes; - } - - @Override - public Result backup( - SecretKey secretKey, byte[] fingerprintMixerSalt, Set<ChunkHash> existingChunks) - throws IOException, GeneralSecurityException { - MessageDigest messageDigest = - MessageDigest.getInstance(BackupEncrypter.MESSAGE_DIGEST_ALGORITHM); - RabinFingerprint64 rabinFingerprint64 = new RabinFingerprint64(); - FingerprintMixer fingerprintMixer = new FingerprintMixer(secretKey, fingerprintMixerSalt); - IsChunkBreakpoint isChunkBreakpoint = - new IsChunkBreakpoint(mAverageChunkSizeBytes - mMinChunkSizeBytes); - ContentDefinedChunker chunker = - new ContentDefinedChunker( - mMinChunkSizeBytes, - mMaxChunkSizeBytes, - rabinFingerprint64, - fingerprintMixer, - isChunkBreakpoint); - ChunkHasher chunkHasher = new ChunkHasher(secretKey); - ChunkEncryptor encryptor = new ChunkEncryptor(secretKey, new SecureRandom()); - Set<ChunkHash> includedChunks = new HashSet<>(); - // New chunks will be added only once to this list, even if they occur multiple times. - List<EncryptedChunk> newChunks = new ArrayList<>(); - // All chunks (including multiple occurrences) will be added to the chunkListing. - List<ChunkHash> chunkListing = new ArrayList<>(); - - includedChunks.addAll(existingChunks); - - chunker.chunkify( - mData, - chunk -> { - messageDigest.update(chunk); - ChunkHash key = chunkHasher.computeHash(chunk); - - if (!includedChunks.contains(key)) { - newChunks.add(encryptor.encrypt(key, chunk)); - includedChunks.add(key); - } - chunkListing.add(key); - }); - - Slog.i( - TAG, - String.format( - "Chunks: %d total, %d unique, %d new", - chunkListing.size(), new HashSet<>(chunkListing).size(), newChunks.size())); - return new Result( - Collections.unmodifiableList(chunkListing), - Collections.unmodifiableList(newChunks), - messageDigest.digest()); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTask.java deleted file mode 100644 index 8f35db69f11e..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTask.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.content.Context; -import android.util.Slog; - -import com.android.server.backup.encryption.CryptoSettings; -import com.android.server.backup.encryption.chunking.ProtoStore; -import com.android.server.backup.encryption.storage.BackupEncryptionDb; -import com.android.server.backup.encryption.storage.EncryptionDbException; - -import java.io.IOException; - -/** - * Task to clear local crypto state. - * - * <p>Needs to run whenever the user changes their backup account. - */ -public class ClearCryptoStateTask { - private static final String TAG = "ClearCryptoStateTask"; - - private final Context mContext; - private final CryptoSettings mCryptoSettings; - - /** - * A new instance. - * - * @param context for finding local storage. - * @param cryptoSettings to clear - */ - public ClearCryptoStateTask(Context context, CryptoSettings cryptoSettings) { - mContext = context; - mCryptoSettings = cryptoSettings; - } - - /** Deletes all local state for backup (not restore). */ - public void run() { - Slog.d(TAG, "Clearing local crypto state."); - try { - BackupEncryptionDb.newInstance(mContext).clear(); - } catch (EncryptionDbException e) { - Slog.e(TAG, "Error clearing encryption database", e); - } - mCryptoSettings.clearAllSettingsForBackup(); - try { - ProtoStore.createChunkListingStore(mContext).deleteAllProtos(); - } catch (IOException e) { - Slog.e(TAG, "Error clearing chunk listing store", e); - } - try { - ProtoStore.createKeyValueListingStore(mContext).deleteAllProtos(); - } catch (IOException e) { - Slog.e(TAG, "Error clearing key-value store", e); - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java deleted file mode 100644 index f67f1007f632..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 java.io.Closeable; -import java.io.IOException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -/** - * Accepts the plaintext bytes of decrypted chunks and writes them to some output. Also keeps track - * of the message digest of the chunks. - */ -public interface DecryptedChunkOutput extends Closeable { - /** - * Opens whatever output the implementation chooses, ready to process chunks. - * - * @return {@code this}, to allow use with try-with-resources - */ - DecryptedChunkOutput open() throws IOException, NoSuchAlgorithmException; - - /** - * Writes the plaintext bytes of chunk to whatever output the implementation chooses. Also - * updates the digest with the chunk. - * - * <p>You must call {@link #open()} before this method, and you may not call it after calling - * {@link Closeable#close()}. - * - * @param plaintextBuffer An array containing the bytes of the plaintext of the chunk, starting - * at index 0. - * @param length The length in bytes of the plaintext contained in {@code plaintextBuffer}. - */ - void processChunk(byte[] plaintextBuffer, int length) - throws IOException, InvalidKeyException, NoSuchAlgorithmException; - - /** - * Returns the message digest of all the chunks processed by {@link #processChunk}. - * - * <p>You must call {@link Closeable#close()} before calling this method. - */ - byte[] getDigest() throws NoSuchAlgorithmException; -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedBackupTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedBackupTask.java deleted file mode 100644 index ef13f23e799d..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedBackupTask.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * 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.annotation.TargetApi; -import android.os.Build.VERSION_CODES; -import android.util.Slog; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.BackupFileBuilder; -import com.android.server.backup.encryption.chunking.EncryptedChunk; -import com.android.server.backup.encryption.client.CryptoBackupServer; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CancellationException; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.GCMParameterSpec; - -/** - * Task which reads encrypted chunks from a {@link BackupEncrypter}, builds a backup file and - * uploads it to the server. - */ -@TargetApi(VERSION_CODES.P) -public class EncryptedBackupTask { - private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; - private static final int GCM_NONCE_LENGTH_BYTES = 12; - private static final int GCM_TAG_LENGTH_BYTES = 16; - private static final int BITS_PER_BYTE = 8; - - private static final String TAG = "EncryptedBackupTask"; - - private final CryptoBackupServer mCryptoBackupServer; - private final SecureRandom mSecureRandom; - private final String mPackageName; - private final ByteArrayOutputStream mBackupDataOutput; - private final BackupEncrypter mBackupEncrypter; - private final AtomicBoolean mCancelled; - - /** Creates a new instance which reads data from the given input stream. */ - public EncryptedBackupTask( - CryptoBackupServer cryptoBackupServer, - SecureRandom secureRandom, - String packageName, - BackupEncrypter backupEncrypter) { - mCryptoBackupServer = cryptoBackupServer; - mSecureRandom = secureRandom; - mPackageName = packageName; - mBackupEncrypter = backupEncrypter; - - mBackupDataOutput = new ByteArrayOutputStream(); - mCancelled = new AtomicBoolean(false); - } - - /** - * Creates a non-incremental backup file and uploads it to the server. - * - * @param fingerprintMixerSalt Fingerprint mixer salt used for content-defined chunking during a - * full backup. May be {@code null} for a key-value backup. - */ - public ChunksMetadataProto.ChunkListing performNonIncrementalBackup( - SecretKey tertiaryKey, - WrappedKeyProto.WrappedKey wrappedTertiaryKey, - @Nullable byte[] fingerprintMixerSalt) - throws IOException, GeneralSecurityException { - - ChunksMetadataProto.ChunkListing newChunkListing = - performBackup( - tertiaryKey, - fingerprintMixerSalt, - BackupFileBuilder.createForNonIncremental(mBackupDataOutput), - new HashSet<>()); - - throwIfCancelled(); - - newChunkListing.documentId = - mCryptoBackupServer.uploadNonIncrementalBackup( - mPackageName, mBackupDataOutput.toByteArray(), wrappedTertiaryKey); - - return newChunkListing; - } - - /** Creates an incremental backup file and uploads it to the server. */ - public ChunksMetadataProto.ChunkListing performIncrementalBackup( - SecretKey tertiaryKey, - WrappedKeyProto.WrappedKey wrappedTertiaryKey, - ChunksMetadataProto.ChunkListing oldChunkListing) - throws IOException, GeneralSecurityException { - - ChunksMetadataProto.ChunkListing newChunkListing = - performBackup( - tertiaryKey, - oldChunkListing.fingerprintMixerSalt, - BackupFileBuilder.createForIncremental(mBackupDataOutput, oldChunkListing), - getChunkHashes(oldChunkListing)); - - throwIfCancelled(); - - String oldDocumentId = oldChunkListing.documentId; - Slog.v(TAG, "Old doc id: " + oldDocumentId); - - newChunkListing.documentId = - mCryptoBackupServer.uploadIncrementalBackup( - mPackageName, - oldDocumentId, - mBackupDataOutput.toByteArray(), - wrappedTertiaryKey); - return newChunkListing; - } - - /** - * Signals to the task that the backup has been cancelled. If the upload has not yet started - * then the task will not upload any data to the server or save the new chunk listing. - */ - public void cancel() { - mCancelled.getAndSet(true); - } - - private void throwIfCancelled() { - if (mCancelled.get()) { - throw new CancellationException("EncryptedBackupTask was cancelled"); - } - } - - private ChunksMetadataProto.ChunkListing performBackup( - SecretKey tertiaryKey, - @Nullable byte[] fingerprintMixerSalt, - BackupFileBuilder backupFileBuilder, - Set<ChunkHash> existingChunkHashes) - throws IOException, GeneralSecurityException { - BackupEncrypter.Result result = - mBackupEncrypter.backup(tertiaryKey, fingerprintMixerSalt, existingChunkHashes); - backupFileBuilder.writeChunks(result.getAllChunks(), buildChunkMap(result.getNewChunks())); - - ChunksMetadataProto.ChunkOrdering chunkOrdering = - backupFileBuilder.getNewChunkOrdering(result.getDigest()); - backupFileBuilder.finish(buildMetadata(tertiaryKey, chunkOrdering)); - - return backupFileBuilder.getNewChunkListing(fingerprintMixerSalt); - } - - /** Returns a set containing the hashes of every chunk in the given listing. */ - private static Set<ChunkHash> getChunkHashes(ChunksMetadataProto.ChunkListing chunkListing) { - Set<ChunkHash> hashes = new HashSet<>(); - for (ChunksMetadataProto.Chunk chunk : chunkListing.chunks) { - hashes.add(new ChunkHash(chunk.hash)); - } - return hashes; - } - - /** Returns a map from chunk hash to chunk containing every chunk in the given list. */ - private static Map<ChunkHash, EncryptedChunk> buildChunkMap(List<EncryptedChunk> chunks) { - Map<ChunkHash, EncryptedChunk> chunkMap = new HashMap<>(); - for (EncryptedChunk chunk : chunks) { - chunkMap.put(chunk.key(), chunk); - } - return chunkMap; - } - - private ChunksMetadataProto.ChunksMetadata buildMetadata( - SecretKey tertiaryKey, ChunksMetadataProto.ChunkOrdering chunkOrdering) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException, NoSuchAlgorithmException, - ShortBufferException, NoSuchPaddingException { - ChunksMetadataProto.ChunksMetadata metaData = new ChunksMetadataProto.ChunksMetadata(); - metaData.cipherType = ChunksMetadataProto.AES_256_GCM; - metaData.checksumType = ChunksMetadataProto.SHA_256; - metaData.chunkOrdering = encryptChunkOrdering(tertiaryKey, chunkOrdering); - return metaData; - } - - private byte[] encryptChunkOrdering( - SecretKey tertiaryKey, ChunksMetadataProto.ChunkOrdering chunkOrdering) - throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - NoSuchPaddingException, NoSuchAlgorithmException, - InvalidAlgorithmParameterException, ShortBufferException { - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - - byte[] nonce = generateNonce(); - - cipher.init( - Cipher.ENCRYPT_MODE, - tertiaryKey, - new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE, nonce)); - - byte[] orderingBytes = ChunksMetadataProto.ChunkOrdering.toByteArray(chunkOrdering); - // We prepend the nonce to the ordering. - byte[] output = - Arrays.copyOf( - nonce, - GCM_NONCE_LENGTH_BYTES + orderingBytes.length + GCM_TAG_LENGTH_BYTES); - - cipher.doFinal( - orderingBytes, - /*inputOffset=*/ 0, - /*inputLen=*/ orderingBytes.length, - output, - /*outputOffset=*/ GCM_NONCE_LENGTH_BYTES); - - return output; - } - - private byte[] generateNonce() { - byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES]; - mSecureRandom.nextBytes(nonce); - return nonce; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessor.java deleted file mode 100644 index 71588f636907..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessor.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * 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 com.android.internal.util.Preconditions.checkState; - -import android.annotation.Nullable; -import android.app.backup.BackupTransport; -import android.content.Context; -import android.util.Slog; - -import com.android.server.backup.encryption.FullBackupDataProcessor; -import com.android.server.backup.encryption.StreamUtils; -import com.android.server.backup.encryption.client.CryptoBackupServer; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.security.SecureRandom; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -/** - * Accepts backup data from a {@link InputStream} and passes it to the encrypted full data backup - * path. - */ -public class EncryptedFullBackupDataProcessor implements FullBackupDataProcessor { - - private static final String TAG = "EncryptedFullBackupDP"; - - private final Context mContext; - private final ExecutorService mExecutorService; - private final CryptoBackupServer mCryptoBackupServer; - private final SecureRandom mSecureRandom; - private final RecoverableKeyStoreSecondaryKey mSecondaryKey; - private final String mPackageName; - - @Nullable private InputStream mInputStream; - @Nullable private PipedOutputStream mOutputStream; - @Nullable private EncryptedFullBackupTask mBackupTask; - @Nullable private Future<Void> mBackupTaskFuture; - @Nullable private FullBackupCallbacks mFullBackupCallbacks; - - public EncryptedFullBackupDataProcessor( - Context context, - ExecutorService executorService, - CryptoBackupServer cryptoBackupServer, - SecureRandom secureRandom, - RecoverableKeyStoreSecondaryKey secondaryKey, - String packageName) { - mContext = Objects.requireNonNull(context); - mExecutorService = Objects.requireNonNull(executorService); - mCryptoBackupServer = Objects.requireNonNull(cryptoBackupServer); - mSecureRandom = Objects.requireNonNull(secureRandom); - mSecondaryKey = Objects.requireNonNull(secondaryKey); - mPackageName = Objects.requireNonNull(packageName); - } - - @Override - public boolean initiate(InputStream inputStream) throws IOException { - checkState(mBackupTask == null, "initiate() twice"); - - this.mInputStream = inputStream; - mOutputStream = new PipedOutputStream(); - - mBackupTask = - EncryptedFullBackupTask.newInstance( - mContext, - mCryptoBackupServer, - mSecureRandom, - mSecondaryKey, - mPackageName, - new PipedInputStream(mOutputStream)); - - return true; - } - - @Override - public void start() { - checkState(mBackupTask != null, "start() before initiate()"); - mBackupTaskFuture = mExecutorService.submit(mBackupTask); - } - - @Override - public int pushData(int numBytes) { - checkState( - mBackupTaskFuture != null && mInputStream != null && mOutputStream != null, - "pushData() before start()"); - - // If the upload has failed then stop without pushing any more bytes. - if (mBackupTaskFuture.isDone()) { - Optional<Exception> exception = getTaskException(); - Slog.e(TAG, "Encrypted upload failed", exception.orElse(null)); - if (exception.isPresent()) { - reportNetworkFailureIfNecessary(exception.get()); - - if (exception.get().getCause() instanceof SizeQuotaExceededException) { - return BackupTransport.TRANSPORT_QUOTA_EXCEEDED; - } - } - - return BackupTransport.TRANSPORT_ERROR; - } - - try { - StreamUtils.copyStream(mInputStream, mOutputStream, numBytes); - } catch (IOException e) { - Slog.e(TAG, "IOException when processing backup", e); - return BackupTransport.TRANSPORT_ERROR; - } - - return BackupTransport.TRANSPORT_OK; - } - - @Override - public void cancel() { - checkState(mBackupTaskFuture != null && mBackupTask != null, "cancel() before start()"); - mBackupTask.cancel(); - closeStreams(); - } - - @Override - public int finish() { - checkState(mBackupTaskFuture != null, "finish() before start()"); - - // getTaskException() waits for the task to finish. We must close the streams first, which - // causes the task to finish, otherwise it will block forever. - closeStreams(); - Optional<Exception> exception = getTaskException(); - - if (exception.isPresent()) { - Slog.e(TAG, "Exception during encrypted full backup", exception.get()); - reportNetworkFailureIfNecessary(exception.get()); - - if (exception.get().getCause() instanceof SizeQuotaExceededException) { - return BackupTransport.TRANSPORT_QUOTA_EXCEEDED; - } - return BackupTransport.TRANSPORT_ERROR; - - } else { - if (mFullBackupCallbacks != null) { - mFullBackupCallbacks.onSuccess(); - } - - return BackupTransport.TRANSPORT_OK; - } - } - - private void closeStreams() { - StreamUtils.closeQuietly(mInputStream); - StreamUtils.closeQuietly(mOutputStream); - } - - @Override - public void handleCheckSizeRejectionZeroBytes() { - cancel(); - } - - @Override - public void handleCheckSizeRejectionQuotaExceeded() { - cancel(); - } - - @Override - public void handleSendBytesQuotaExceeded() { - cancel(); - } - - @Override - public void attachCallbacks(FullBackupCallbacks fullBackupCallbacks) { - this.mFullBackupCallbacks = fullBackupCallbacks; - } - - private void reportNetworkFailureIfNecessary(Exception exception) { - if (!(exception.getCause() instanceof SizeQuotaExceededException) - && mFullBackupCallbacks != null) { - mFullBackupCallbacks.onTransferFailed(); - } - } - - private Optional<Exception> getTaskException() { - if (mBackupTaskFuture != null) { - try { - mBackupTaskFuture.get(); - } catch (InterruptedException | ExecutionException e) { - return Optional.of(e); - } - } - return Optional.empty(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTask.java deleted file mode 100644 index a938d715a307..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTask.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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.content.Context; -import android.util.Slog; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.backup.encryption.StreamUtils; -import com.android.server.backup.encryption.chunking.ProtoStore; -import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer; -import com.android.server.backup.encryption.client.CryptoBackupServer; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; -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.ChunkListing; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import java.io.IOException; -import java.io.InputStream; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Optional; -import java.util.concurrent.Callable; - -import javax.crypto.SecretKey; - -/** - * Task which reads a stream of plaintext full backup data, chunks it, encrypts it and uploads it to - * the server. - * - * <p>Once the backup completes or fails, closes the input stream. - */ -public class EncryptedFullBackupTask implements Callable<Void> { - private static final String TAG = "EncryptedFullBackupTask"; - - private static final int MIN_CHUNK_SIZE_BYTES = 2 * 1024; - private static final int MAX_CHUNK_SIZE_BYTES = 64 * 1024; - private static final int AVERAGE_CHUNK_SIZE_BYTES = 4 * 1024; - - // TODO(b/69350270): Remove this hard-coded salt and related logic once we feel confident that - // incremental backup has happened at least once for all existing packages/users since we moved - // to - // using a randomly generated salt. - // - // The hard-coded fingerprint mixer salt was used for a short time period before replaced by one - // that is randomly generated on initial non-incremental backup and stored in ChunkListing to be - // reused for succeeding incremental backups. If an old ChunkListing does not have a - // fingerprint_mixer_salt, we assume that it was last backed up before a randomly generated salt - // is used so we use the hardcoded salt and set ChunkListing#fingerprint_mixer_salt to this - // value. - // Eventually all backup ChunkListings will have this field set and then we can remove the - // default - // value in the code. - static final byte[] DEFAULT_FINGERPRINT_MIXER_SALT = - Arrays.copyOf(new byte[] {20, 23}, FingerprintMixer.SALT_LENGTH_BYTES); - - private final ProtoStore<ChunkListing> mChunkListingStore; - private final TertiaryKeyManager mTertiaryKeyManager; - private final InputStream mInputStream; - private final EncryptedBackupTask mTask; - private final String mPackageName; - private final SecureRandom mSecureRandom; - - /** Creates a new instance with the default min, max and average chunk sizes. */ - public static EncryptedFullBackupTask newInstance( - Context context, - CryptoBackupServer cryptoBackupServer, - SecureRandom secureRandom, - RecoverableKeyStoreSecondaryKey secondaryKey, - String packageName, - InputStream inputStream) - throws IOException { - EncryptedBackupTask encryptedBackupTask = - new EncryptedBackupTask( - cryptoBackupServer, - secureRandom, - packageName, - new BackupStreamEncrypter( - inputStream, - MIN_CHUNK_SIZE_BYTES, - MAX_CHUNK_SIZE_BYTES, - AVERAGE_CHUNK_SIZE_BYTES)); - TertiaryKeyManager tertiaryKeyManager = - new TertiaryKeyManager( - context, - secureRandom, - TertiaryKeyRotationScheduler.getInstance(context), - secondaryKey, - packageName); - - return new EncryptedFullBackupTask( - ProtoStore.createChunkListingStore(context), - tertiaryKeyManager, - encryptedBackupTask, - inputStream, - packageName, - new SecureRandom()); - } - - @VisibleForTesting - EncryptedFullBackupTask( - ProtoStore<ChunkListing> chunkListingStore, - TertiaryKeyManager tertiaryKeyManager, - EncryptedBackupTask task, - InputStream inputStream, - String packageName, - SecureRandom secureRandom) { - mChunkListingStore = chunkListingStore; - mTertiaryKeyManager = tertiaryKeyManager; - mInputStream = inputStream; - mTask = task; - mPackageName = packageName; - mSecureRandom = secureRandom; - } - - @Override - public Void call() throws Exception { - try { - Optional<ChunkListing> maybeOldChunkListing = - mChunkListingStore.loadProto(mPackageName); - - if (maybeOldChunkListing.isPresent()) { - Slog.i(TAG, "Found previous chunk listing for " + mPackageName); - } - - // If the key has been rotated then we must re-encrypt all of the backup data. - if (mTertiaryKeyManager.wasKeyRotated()) { - Slog.i( - TAG, - "Key was rotated or newly generated for " - + mPackageName - + ", so performing a full backup."); - maybeOldChunkListing = Optional.empty(); - mChunkListingStore.deleteProto(mPackageName); - } - - SecretKey tertiaryKey = mTertiaryKeyManager.getKey(); - WrappedKeyProto.WrappedKey wrappedTertiaryKey = mTertiaryKeyManager.getWrappedKey(); - - ChunkListing newChunkListing; - if (!maybeOldChunkListing.isPresent()) { - byte[] fingerprintMixerSalt = new byte[FingerprintMixer.SALT_LENGTH_BYTES]; - mSecureRandom.nextBytes(fingerprintMixerSalt); - newChunkListing = - mTask.performNonIncrementalBackup( - tertiaryKey, wrappedTertiaryKey, fingerprintMixerSalt); - } else { - ChunkListing oldChunkListing = maybeOldChunkListing.get(); - - if (oldChunkListing.fingerprintMixerSalt == null - || oldChunkListing.fingerprintMixerSalt.length == 0) { - oldChunkListing.fingerprintMixerSalt = DEFAULT_FINGERPRINT_MIXER_SALT; - } - - newChunkListing = - mTask.performIncrementalBackup( - tertiaryKey, wrappedTertiaryKey, oldChunkListing); - } - - mChunkListingStore.saveProto(mPackageName, newChunkListing); - Slog.v(TAG, "Saved chunk listing for " + mPackageName); - } catch (IOException e) { - Slog.e(TAG, "Storage exception, wiping state"); - mChunkListingStore.deleteProto(mPackageName); - throw e; - } finally { - StreamUtils.closeQuietly(mInputStream); - } - - return null; - } - - /** - * Signals to the task that the backup has been cancelled. If the upload has not yet started - * then the task will not upload any data to the server or save the new chunk listing. - * - * <p>You must then terminate the input stream. - */ - public void cancel() { - mTask.cancel(); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTask.java deleted file mode 100644 index 04381af561b2..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTask.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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 com.android.internal.util.Preconditions.checkArgument; - -import android.annotation.Nullable; -import android.content.Context; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.backup.encryption.FullRestoreDataProcessor; -import com.android.server.backup.encryption.FullRestoreDownloader; -import com.android.server.backup.encryption.StreamUtils; -import com.android.server.backup.encryption.chunking.DecryptedChunkFileOutput; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; - -/** Downloads the encrypted backup file, decrypts it and passes the data to backup manager. */ -public class EncryptedFullRestoreTask implements FullRestoreDataProcessor { - private static final String DEFAULT_TEMPORARY_FOLDER = "encrypted_restore_temp"; - private static final String ENCRYPTED_FILE_NAME = "encrypted_restore"; - private static final String DECRYPTED_FILE_NAME = "decrypted_restore"; - - private final FullRestoreToFileTask mFullRestoreToFileTask; - private final BackupFileDecryptorTask mBackupFileDecryptorTask; - private final File mEncryptedFile; - private final File mDecryptedFile; - @Nullable private InputStream mDecryptedFileInputStream; - - /** - * Creates a new task which stores temporary files in the files directory. - * - * @param fullRestoreDownloader which will download the backup file - * @param tertiaryKey which the backup file is encrypted with - */ - public static EncryptedFullRestoreTask newInstance( - Context context, FullRestoreDownloader fullRestoreDownloader, SecretKey tertiaryKey) - throws NoSuchAlgorithmException, NoSuchPaddingException { - File temporaryFolder = new File(context.getFilesDir(), DEFAULT_TEMPORARY_FOLDER); - temporaryFolder.mkdirs(); - return new EncryptedFullRestoreTask( - temporaryFolder, fullRestoreDownloader, new BackupFileDecryptorTask(tertiaryKey)); - } - - @VisibleForTesting - EncryptedFullRestoreTask( - File temporaryFolder, - FullRestoreDownloader fullRestoreDownloader, - BackupFileDecryptorTask backupFileDecryptorTask) { - checkArgument(temporaryFolder.isDirectory(), "Temporary folder must be existing directory"); - - mEncryptedFile = new File(temporaryFolder, ENCRYPTED_FILE_NAME); - mDecryptedFile = new File(temporaryFolder, DECRYPTED_FILE_NAME); - - mFullRestoreToFileTask = new FullRestoreToFileTask(fullRestoreDownloader); - mBackupFileDecryptorTask = backupFileDecryptorTask; - } - - /** - * Reads the next decrypted bytes into the given buffer. - * - * <p>During the first call this method will download the backup file from the server, decrypt - * it and save it to disk. It will then read the bytes from the file on disk. - * - * <p>Once this method has read all the bytes of the file, the caller must call {@link #finish} - * to clean up. - * - * @return the number of bytes read, or {@code -1} on reaching the end of the file - */ - @Override - public int readNextChunk(byte[] buffer) throws IOException { - if (mDecryptedFileInputStream == null) { - try { - mDecryptedFileInputStream = downloadAndDecryptBackup(); - } catch (BadPaddingException - | InvalidKeyException - | NoSuchAlgorithmException - | IllegalBlockSizeException - | ShortBufferException - | EncryptedRestoreException - | InvalidAlgorithmParameterException e) { - throw new IOException("Encryption issue", e); - } - } - - return mDecryptedFileInputStream.read(buffer); - } - - private InputStream downloadAndDecryptBackup() - throws IOException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, - IllegalBlockSizeException, ShortBufferException, EncryptedRestoreException, - InvalidAlgorithmParameterException { - mFullRestoreToFileTask.restoreToFile(mEncryptedFile); - mBackupFileDecryptorTask.decryptFile( - mEncryptedFile, new DecryptedChunkFileOutput(mDecryptedFile)); - mEncryptedFile.delete(); - return new BufferedInputStream(new FileInputStream(mDecryptedFile)); - } - - /** Cleans up temporary files. */ - @Override - public void finish(FullRestoreDownloader.FinishType unusedFinishType) { - // The download is finished and log sent during RestoreToFileTask#restoreToFile(), so we - // don't need to do either of those things here. - - StreamUtils.closeQuietly(mDecryptedFileInputStream); - mEncryptedFile.delete(); - mDecryptedFile.delete(); - } -} 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 deleted file mode 100644 index 619438c7f6fe..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTask.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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/EncryptedKvRestoreTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedKvRestoreTask.java deleted file mode 100644 index 12b44590ebe6..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedKvRestoreTask.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * 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 com.android.internal.util.Preconditions.checkArgument; - -import android.app.backup.BackupDataOutput; -import android.content.Context; -import android.os.ParcelFileDescriptor; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.backup.encryption.FullRestoreDownloader; -import com.android.server.backup.encryption.chunking.ChunkHasher; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; -import com.android.server.backup.encryption.keys.RestoreKeyFetcher; -import com.android.server.backup.encryption.kv.DecryptedChunkKvOutput; -import com.android.server.backup.encryption.protos.nano.KeyValuePairProto; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import java.io.File; -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyException; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.ShortBufferException; - -/** - * Performs a key value restore by downloading the backup set, decrypting it and writing it to the - * file provided by backup manager. - */ -public class EncryptedKvRestoreTask { - private static final String ENCRYPTED_FILE_NAME = "encrypted_kv"; - - private final File mTemporaryFolder; - private final ChunkHasher mChunkHasher; - private final FullRestoreToFileTask mFullRestoreToFileTask; - private final BackupFileDecryptorTask mBackupFileDecryptorTask; - - /** Constructs new instances of the task. */ - public static class EncryptedKvRestoreTaskFactory { - /** - * Constructs a new instance. - * - * <p>Fetches the appropriate secondary key and uses this to unwrap the tertiary key. Stores - * temporary files in {@link Context#getFilesDir()}. - */ - public EncryptedKvRestoreTask newInstance( - Context context, - RecoverableKeyStoreSecondaryKeyManager - .RecoverableKeyStoreSecondaryKeyManagerProvider - recoverableSecondaryKeyManagerProvider, - FullRestoreDownloader fullRestoreDownloader, - String secondaryKeyAlias, - WrappedKeyProto.WrappedKey wrappedTertiaryKey) - throws EncryptedRestoreException, NoSuchAlgorithmException, NoSuchPaddingException, - KeyException, InvalidAlgorithmParameterException { - SecretKey tertiaryKey = - RestoreKeyFetcher.unwrapTertiaryKey( - recoverableSecondaryKeyManagerProvider, - secondaryKeyAlias, - wrappedTertiaryKey); - - return new EncryptedKvRestoreTask( - context.getFilesDir(), - new ChunkHasher(tertiaryKey), - new FullRestoreToFileTask(fullRestoreDownloader), - new BackupFileDecryptorTask(tertiaryKey)); - } - } - - @VisibleForTesting - EncryptedKvRestoreTask( - File temporaryFolder, - ChunkHasher chunkHasher, - FullRestoreToFileTask fullRestoreToFileTask, - BackupFileDecryptorTask backupFileDecryptorTask) { - checkArgument( - temporaryFolder.isDirectory(), "Temporary folder must be an existing directory"); - - mTemporaryFolder = temporaryFolder; - mChunkHasher = chunkHasher; - mFullRestoreToFileTask = fullRestoreToFileTask; - mBackupFileDecryptorTask = backupFileDecryptorTask; - } - - /** - * Runs the restore, writing the pairs in lexicographical order to the given file descriptor. - * - * <p>This will block for the duration of the restore. - * - * @throws EncryptedRestoreException if there is a problem decrypting or verifying the backup - */ - public void getRestoreData(ParcelFileDescriptor output) - throws IOException, EncryptedRestoreException, BadPaddingException, - InvalidAlgorithmParameterException, NoSuchAlgorithmException, - IllegalBlockSizeException, ShortBufferException, InvalidKeyException { - File encryptedFile = new File(mTemporaryFolder, ENCRYPTED_FILE_NAME); - try { - downloadDecryptAndWriteBackup(encryptedFile, output); - } finally { - encryptedFile.delete(); - } - } - - private void downloadDecryptAndWriteBackup(File encryptedFile, ParcelFileDescriptor output) - throws EncryptedRestoreException, IOException, BadPaddingException, InvalidKeyException, - NoSuchAlgorithmException, IllegalBlockSizeException, ShortBufferException, - InvalidAlgorithmParameterException { - mFullRestoreToFileTask.restoreToFile(encryptedFile); - DecryptedChunkKvOutput decryptedChunkKvOutput = new DecryptedChunkKvOutput(mChunkHasher); - mBackupFileDecryptorTask.decryptFile(encryptedFile, decryptedChunkKvOutput); - - BackupDataOutput backupDataOutput = new BackupDataOutput(output.getFileDescriptor()); - for (KeyValuePairProto.KeyValuePair pair : decryptedChunkKvOutput.getPairs()) { - backupDataOutput.writeEntityHeader(pair.key, pair.value.length); - backupDataOutput.writeEntityData(pair.value, pair.value.length); - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java deleted file mode 100644 index 487c0d92f6fd..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedRestoreException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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; - -/** Wraps any exception related to encryption which occurs during restore. */ -public class EncryptedRestoreException extends Exception { - public EncryptedRestoreException(String message) { - super(message); - } - - public EncryptedRestoreException(Throwable cause) { - super(cause); - } - - public EncryptedRestoreException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java deleted file mode 100644 index 82f83f9b7494..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTask.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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 com.android.internal.util.Preconditions.checkArgument; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.server.backup.encryption.FullRestoreDownloader; -import com.android.server.backup.encryption.FullRestoreDownloader.FinishType; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -/** - * Reads a stream from a {@link FullRestoreDownloader} and writes it to a file for consumption by - * {@link BackupFileDecryptorTask}. - */ -public class FullRestoreToFileTask { - /** - * Maximum number of bytes which the framework can request from the full restore data stream in - * one call to {@link BackupTransport#getNextFullRestoreDataChunk}. - */ - public static final int MAX_BYTES_FULL_RESTORE_CHUNK = 1024 * 32; - - /** Returned when the end of a backup stream has been reached. */ - private static final int END_OF_STREAM = -1; - - private final FullRestoreDownloader mFullRestoreDownloader; - private final int mBufferSize; - - /** - * Constructs a new instance which reads from the given package wrapper, using a buffer of size - * {@link #MAX_BYTES_FULL_RESTORE_CHUNK}. - */ - public FullRestoreToFileTask(FullRestoreDownloader fullRestoreDownloader) { - this(fullRestoreDownloader, MAX_BYTES_FULL_RESTORE_CHUNK); - } - - @VisibleForTesting - FullRestoreToFileTask(FullRestoreDownloader fullRestoreDownloader, int bufferSize) { - checkArgument(bufferSize > 0, "Buffer must have positive size"); - - this.mFullRestoreDownloader = fullRestoreDownloader; - this.mBufferSize = bufferSize; - } - - /** - * Downloads the backup file from the server and writes it to the given file. - * - * <p>At the end of the download (success or failure), closes the connection and sends a - * Clearcut log. - */ - public void restoreToFile(File targetFile) throws IOException { - try (BufferedOutputStream outputStream = - new BufferedOutputStream(new FileOutputStream(targetFile))) { - byte[] buffer = new byte[mBufferSize]; - int bytesRead = mFullRestoreDownloader.readNextChunk(buffer); - while (bytesRead != END_OF_STREAM) { - outputStream.write(buffer, /* off=*/ 0, bytesRead); - bytesRead = mFullRestoreDownloader.readNextChunk(buffer); - } - - outputStream.flush(); - - mFullRestoreDownloader.finish(FinishType.FINISHED); - } catch (IOException e) { - mFullRestoreDownloader.finish(FinishType.TRANSFER_FAILURE); - throw e; - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTask.java deleted file mode 100644 index d436554341b8..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTask.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.content.Context; -import android.security.keystore.recovery.InternalRecoveryServiceException; -import android.security.keystore.recovery.LockScreenRequiredException; -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.RecoverableKeyStoreSecondaryKey; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; - -import java.security.InvalidKeyException; -import java.security.UnrecoverableKeyException; -import java.util.Collections; -import java.util.Optional; - -/** - * Initializes the device for encrypted backup, through generating a secondary key, and setting its - * alias in the settings. - * - * <p>If the device is already initialized, this is a no-op. - */ -public class InitializeRecoverableSecondaryKeyTask { - private static final String TAG = "InitializeRecoverableSecondaryKeyTask"; - - private final Context mContext; - private final CryptoSettings mCryptoSettings; - private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; - private final CryptoBackupServer mBackupServer; - - /** - * A new instance. - * - * @param cryptoSettings Settings to store the active key alias. - * @param secondaryKeyManager Key manager to generate the new active secondary key. - * @param backupServer Server with which to sync the active key alias. - */ - public InitializeRecoverableSecondaryKeyTask( - Context context, - CryptoSettings cryptoSettings, - RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager, - CryptoBackupServer backupServer) { - mContext = context; - mCryptoSettings = cryptoSettings; - mSecondaryKeyManager = secondaryKeyManager; - mBackupServer = backupServer; - } - - /** - * Initializes the device for encrypted backup, by generating a recoverable secondary key, then - * sending that alias to the backup server and saving it in local settings. - * - * <p>If there is already an active secondary key then does nothing. If the active secondary key - * is destroyed then throws {@link InvalidKeyException}. - * - * <p>If a key rotation is pending and able to finish (i.e., the new key has synced with the - * remote trusted hardware module), then it completes the rotation before returning the key. - * - * @return The active secondary key. - * @throws InvalidKeyException if the secondary key is in a bad state. - */ - public RecoverableKeyStoreSecondaryKey run() - throws InvalidKeyException, LockScreenRequiredException, UnrecoverableKeyException, - InternalRecoveryServiceException { - // Complete any pending key rotations - new RotateSecondaryKeyTask( - mContext, - mSecondaryKeyManager, - mBackupServer, - mCryptoSettings, - RecoveryController.getInstance(mContext)) - .run(); - - return runInternal(); - } - - private RecoverableKeyStoreSecondaryKey runInternal() - throws InvalidKeyException, LockScreenRequiredException, UnrecoverableKeyException, - InternalRecoveryServiceException { - Optional<RecoverableKeyStoreSecondaryKey> maybeActiveKey = loadFromSetting(); - - if (maybeActiveKey.isPresent()) { - assertKeyNotDestroyed(maybeActiveKey.get()); - Slog.d(TAG, "Secondary key already initialized: " + maybeActiveKey.get().getAlias()); - return maybeActiveKey.get(); - } - - Slog.v(TAG, "Initializing for crypto: generating a secondary key."); - RecoverableKeyStoreSecondaryKey key = mSecondaryKeyManager.generate(); - - String alias = key.getAlias(); - Slog.i(TAG, "Generated new secondary key " + alias); - - // No tertiary keys yet as we are creating a brand new secondary (without rotation). - mBackupServer.setActiveSecondaryKeyAlias(alias, /*tertiaryKeys=*/ Collections.emptyMap()); - Slog.v(TAG, "Successfully synced %s " + alias + " with server."); - - mCryptoSettings.initializeWithKeyAlias(alias); - Slog.v(TAG, "Successfully saved " + alias + " as active secondary to disk."); - - return key; - } - - private void assertKeyNotDestroyed(RecoverableKeyStoreSecondaryKey key) - throws InvalidKeyException { - if (key.getStatus(mContext) == RecoverableKeyStoreSecondaryKey.Status.DESTROYED) { - throw new InvalidKeyException("Key destroyed: " + key.getAlias()); - } - } - - private Optional<RecoverableKeyStoreSecondaryKey> loadFromSetting() - throws InvalidKeyException, UnrecoverableKeyException, - InternalRecoveryServiceException { - - // TODO: b/141856950. - if (!mCryptoSettings.getIsInitialized()) { - return Optional.empty(); - } - - Optional<String> maybeAlias = mCryptoSettings.getActiveSecondaryKeyAlias(); - if (!maybeAlias.isPresent()) { - throw new InvalidKeyException( - "Settings said crypto was initialized, but there was no active secondary" - + " alias"); - } - - String alias = maybeAlias.get(); - - Optional<RecoverableKeyStoreSecondaryKey> key; - key = mSecondaryKeyManager.get(alias); - - if (!key.isPresent()) { - throw new InvalidKeyException( - "Initialized with key but it was not in key store: " + alias); - } - - return key; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/KvBackupEncrypter.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/KvBackupEncrypter.java deleted file mode 100644 index d20cd4c07f88..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/KvBackupEncrypter.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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 com.android.internal.util.Preconditions.checkState; - -import android.annotation.Nullable; -import android.app.backup.BackupDataInput; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.ChunkEncryptor; -import com.android.server.backup.encryption.chunking.ChunkHasher; -import com.android.server.backup.encryption.chunking.EncryptedChunk; -import com.android.server.backup.encryption.kv.KeyValueListingBuilder; -import com.android.server.backup.encryption.protos.nano.KeyValueListingProto; -import com.android.server.backup.encryption.protos.nano.KeyValuePairProto; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.SecretKey; - -/** - * Reads key value backup data from an input, converts each pair into a chunk and encrypts the - * chunks. - * - * <p>The caller should pass in the key value listing from the previous backup, if there is one. - * This class emits chunks for both existing and new pairs, using the provided listing to - * determine the hashes of pairs that already exist. During the backup it computes the new listing, - * which the caller should store on disk and pass in at the start of the next backup. - * - * <p>Also computes the message digest, which is {@code SHA-256(chunk hashes sorted - * lexicographically)}. - */ -public class KvBackupEncrypter implements BackupEncrypter { - private final BackupDataInput mBackupDataInput; - - private KeyValueListingProto.KeyValueListing mOldKeyValueListing; - @Nullable private KeyValueListingBuilder mNewKeyValueListing; - - /** - * Constructs a new instance which reads data from the given input. - * - * <p>By default this performs non-incremental backup, call {@link #setOldKeyValueListing} to - * perform incremental backup. - */ - public KvBackupEncrypter(BackupDataInput backupDataInput) { - mBackupDataInput = backupDataInput; - mOldKeyValueListing = KeyValueListingBuilder.emptyListing(); - } - - /** Sets the old listing to perform incremental backup against. */ - public void setOldKeyValueListing(KeyValueListingProto.KeyValueListing oldKeyValueListing) { - mOldKeyValueListing = oldKeyValueListing; - } - - @Override - public Result backup( - SecretKey secretKey, - @Nullable byte[] unusedFingerprintMixerSalt, - Set<ChunkHash> unusedExistingChunks) - throws IOException, GeneralSecurityException { - ChunkHasher chunkHasher = new ChunkHasher(secretKey); - ChunkEncryptor chunkEncryptor = new ChunkEncryptor(secretKey, new SecureRandom()); - mNewKeyValueListing = new KeyValueListingBuilder(); - List<ChunkHash> allChunks = new ArrayList<>(); - List<EncryptedChunk> newChunks = new ArrayList<>(); - - Map<String, ChunkHash> existingChunksToReuse = buildPairMap(mOldKeyValueListing); - - while (mBackupDataInput.readNextHeader()) { - String key = mBackupDataInput.getKey(); - Optional<byte[]> value = readEntireValue(mBackupDataInput); - - // As this pair exists in the new backup, we don't need to add it from the previous - // backup. - existingChunksToReuse.remove(key); - - // If the value is not present then this key has been deleted. - if (value.isPresent()) { - EncryptedChunk newChunk = - createEncryptedChunk(chunkHasher, chunkEncryptor, key, value.get()); - allChunks.add(newChunk.key()); - newChunks.add(newChunk); - mNewKeyValueListing.addPair(key, newChunk.key()); - } - } - - allChunks.addAll(existingChunksToReuse.values()); - - mNewKeyValueListing.addAll(existingChunksToReuse); - - return new Result(allChunks, newChunks, createMessageDigest(allChunks)); - } - - /** - * Returns a listing containing the pairs in the new backup. - * - * <p>You must call {@link #backup} first. - */ - public KeyValueListingProto.KeyValueListing getNewKeyValueListing() { - checkState(mNewKeyValueListing != null, "Must call backup() first"); - return mNewKeyValueListing.build(); - } - - private static Map<String, ChunkHash> buildPairMap( - KeyValueListingProto.KeyValueListing listing) { - Map<String, ChunkHash> map = new HashMap<>(); - for (KeyValueListingProto.KeyValueEntry entry : listing.entries) { - map.put(entry.key, new ChunkHash(entry.hash)); - } - return map; - } - - private EncryptedChunk createEncryptedChunk( - ChunkHasher chunkHasher, ChunkEncryptor chunkEncryptor, String key, byte[] value) - throws InvalidKeyException, IllegalBlockSizeException { - KeyValuePairProto.KeyValuePair pair = new KeyValuePairProto.KeyValuePair(); - pair.key = key; - pair.value = Arrays.copyOf(value, value.length); - - byte[] plaintext = KeyValuePairProto.KeyValuePair.toByteArray(pair); - return chunkEncryptor.encrypt(chunkHasher.computeHash(plaintext), plaintext); - } - - private static byte[] createMessageDigest(List<ChunkHash> allChunks) - throws NoSuchAlgorithmException { - MessageDigest messageDigest = - MessageDigest.getInstance(BackupEncrypter.MESSAGE_DIGEST_ALGORITHM); - // TODO:b/141531271 Extract sorted chunks code to utility class - List<ChunkHash> sortedChunks = new ArrayList<>(allChunks); - Collections.sort(sortedChunks); - for (ChunkHash hash : sortedChunks) { - messageDigest.update(hash.getHash()); - } - return messageDigest.digest(); - } - - private static Optional<byte[]> readEntireValue(BackupDataInput input) throws IOException { - // A negative data size indicates that this key should be deleted. - if (input.getDataSize() < 0) { - return Optional.empty(); - } - - byte[] value = new byte[input.getDataSize()]; - int bytesRead = 0; - while (bytesRead < value.length) { - bytesRead += input.readEntityData(value, bytesRead, value.length - bytesRead); - } - return Optional.of(value); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MalformedEncryptedFileException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MalformedEncryptedFileException.java deleted file mode 100644 index 78c370b0d548..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MalformedEncryptedFileException.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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; - -/** Exception thrown when we cannot parse the encrypted backup file. */ -public class MalformedEncryptedFileException extends EncryptedRestoreException { - public MalformedEncryptedFileException(String message) { - super(message); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MessageDigestMismatchException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MessageDigestMismatchException.java deleted file mode 100644 index 1e4f43b43e26..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/MessageDigestMismatchException.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 the message digest of the plaintext backup does not match that in the {@link - * com.android.server.backup.encryption.protos.ChunksMetadataProto.ChunkOrdering}. - */ -public class MessageDigestMismatchException extends EncryptedRestoreException { - public MessageDigestMismatchException(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 deleted file mode 100644 index 72e8a89f1df3..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NoActiveSecondaryKeyException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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/NonIncrementalBackupRequiredException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NonIncrementalBackupRequiredException.java deleted file mode 100644 index a3eda7d1270f..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/NonIncrementalBackupRequiredException.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * 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 {} 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 deleted file mode 100644 index e5e2c1c3b4cb..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * 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 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.Objects; -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 = Objects.requireNonNull(secondaryKeyManager); - mCryptoSettings = Objects.requireNonNull(cryptoSettings); - mBackupServer = Objects.requireNonNull(backupServer); - mRecoveryController = Objects.requireNonNull(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; - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/SizeQuotaExceededException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/SizeQuotaExceededException.java deleted file mode 100644 index 515db86b6687..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/SizeQuotaExceededException.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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; - -/** Exception thrown when aa backup has exceeded the space allowed for that user */ -public class SizeQuotaExceededException extends RuntimeException { - public SizeQuotaExceededException() { - super("Backup size quota exceeded."); - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java deleted file mode 100644 index 81169e269917..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.security.keystore.recovery.InternalRecoveryServiceException; -import android.security.keystore.recovery.LockScreenRequiredException; -import android.util.Slog; - -import com.android.internal.util.Preconditions; -import com.android.server.backup.encryption.CryptoSettings; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; - -import java.security.UnrecoverableKeyException; -import java.util.Objects; -import java.util.Optional; - -/** - * Starts rotating to a new secondary key. Cannot complete until the screen is unlocked and the new - * key is synced. - */ -public class StartSecondaryKeyRotationTask { - private static final String TAG = "BE-StSecondaryKeyRotTsk"; - - private final CryptoSettings mCryptoSettings; - private final RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; - - public StartSecondaryKeyRotationTask( - CryptoSettings cryptoSettings, - RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager) { - mCryptoSettings = Objects.requireNonNull(cryptoSettings); - mSecondaryKeyManager = Objects.requireNonNull(secondaryKeyManager); - } - - /** Begin the key rotation */ - public void run() { - Slog.i(TAG, "Attempting to initiate a secondary key rotation."); - - Optional<String> maybeCurrentAlias = mCryptoSettings.getActiveSecondaryKeyAlias(); - if (!maybeCurrentAlias.isPresent()) { - Slog.w(TAG, "No active current alias. Cannot trigger a secondary rotation."); - return; - } - String currentAlias = maybeCurrentAlias.get(); - - Optional<String> maybeNextAlias = mCryptoSettings.getNextSecondaryKeyAlias(); - if (maybeNextAlias.isPresent()) { - String nextAlias = maybeNextAlias.get(); - if (nextAlias.equals(currentAlias)) { - // Shouldn't be possible, but guard against accidentally deleting the active key. - Slog.e(TAG, "Was already trying to rotate to what is already the active key."); - } else { - Slog.w(TAG, "Was already rotating to another key. Cancelling that."); - try { - mSecondaryKeyManager.remove(nextAlias); - } catch (Exception e) { - Slog.wtf(TAG, "Could not remove old key", e); - } - } - mCryptoSettings.removeNextSecondaryKeyAlias(); - } - - RecoverableKeyStoreSecondaryKey newSecondaryKey; - try { - newSecondaryKey = mSecondaryKeyManager.generate(); - } catch (LockScreenRequiredException e) { - Slog.e(TAG, "No lock screen is set - cannot generate a new key to rotate to.", e); - return; - } catch (InternalRecoveryServiceException e) { - Slog.e(TAG, "Internal error in Recovery Controller, failed to rotate key.", e); - return; - } catch (UnrecoverableKeyException e) { - Slog.e(TAG, "Failed to get key after generating, failed to rotate", e); - return; - } - - String alias = newSecondaryKey.getAlias(); - Slog.i(TAG, "Generated a new secondary key with alias '" + alias + "'."); - try { - mCryptoSettings.setNextSecondaryAlias(alias); - Slog.i(TAG, "Successfully set '" + alias + "' as next key to rotate to"); - } catch (IllegalArgumentException e) { - Slog.e(TAG, "Unexpected error setting next alias", e); - try { - mSecondaryKeyManager.remove(alias); - } catch (Exception err) { - Slog.wtf(TAG, "Failed to remove generated key after encountering error", err); - } - } - } -} diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/UnsupportedEncryptedFileException.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/UnsupportedEncryptedFileException.java deleted file mode 100644 index 9a97e3870d83..000000000000 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/UnsupportedEncryptedFileException.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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; - -/** - * Thrown when the backup file provided by the server uses encryption algorithms this version of - * backup does not support. This could happen if the backup was created with a newer version of the - * code. - */ -public class UnsupportedEncryptedFileException extends EncryptedRestoreException { - public UnsupportedEncryptedFileException(String message) { - super(message); - } -} diff --git a/packages/BackupEncryption/test/robolectric-integration/Android.bp b/packages/BackupEncryption/test/robolectric-integration/Android.bp deleted file mode 100644 index c842e425fd6f..000000000000 --- a/packages/BackupEncryption/test/robolectric-integration/Android.bp +++ /dev/null @@ -1,43 +0,0 @@ -// 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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_robolectric_test { - name: "BackupEncryptionRoboIntegTests", - srcs: [ - "src/**/*.java", - ], - java_resource_dirs: ["config"], - libs: [ - "backup-encryption-protos", - "platform-test-annotations", - "testng", - "truth-prebuilt", - "BackupEncryptionRoboTests", - ], - static_libs: [ - "androidx.test.core", - "androidx.test.runner", - "androidx.test.rules", - ], - instrumentation_for: "BackupEncryption", -} diff --git a/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml b/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml deleted file mode 100644 index c3930cc7c4f1..000000000000 --- a/packages/BackupEncryption/test/robolectric-integration/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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" - coreApp="true" - package="com.android.server.backup.encryption.robointeg"> - - <application/> - -</manifest> diff --git a/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties b/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties deleted file mode 100644 index 26fceb3f84a4..000000000000 --- a/packages/BackupEncryption/test/robolectric-integration/config/robolectric.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# 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. -# - -sdk=NEWEST_SDK diff --git a/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java b/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java deleted file mode 100644 index a432d91828cf..000000000000 --- a/packages/BackupEncryption/test/robolectric-integration/src/com/android/server/backup/encryption/RoundTripTest.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * 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.google.common.truth.Truth.assertThat; - -import android.content.Context; -import android.os.ParcelFileDescriptor; -import android.security.keystore.recovery.InternalRecoveryServiceException; -import android.security.keystore.recovery.RecoveryController; - -import androidx.test.core.app.ApplicationProvider; - -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.TertiaryKeyManager; -import com.android.server.backup.encryption.keys.TertiaryKeyRotationScheduler; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; -import com.android.server.backup.encryption.tasks.EncryptedFullBackupTask; -import com.android.server.backup.encryption.tasks.EncryptedFullRestoreTask; -import com.android.server.backup.encryption.tasks.EncryptedKvBackupTask; -import com.android.server.backup.encryption.tasks.EncryptedKvRestoreTask; -import com.android.server.testing.shadows.DataEntity; -import com.android.server.testing.shadows.ShadowBackupDataInput; -import com.android.server.testing.shadows.ShadowBackupDataOutput; -import com.android.server.testing.shadows.ShadowRecoveryController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Optional; -import java.util.Map; -import java.util.Set; - -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; - -@Config( - shadows = { - ShadowBackupDataInput.class, - ShadowBackupDataOutput.class, - ShadowRecoveryController.class - }) -@RunWith(RobolectricTestRunner.class) -public class RoundTripTest { - private static final DataEntity[] KEY_VALUE_DATA = { - new DataEntity("test_key_1", "test_value_1"), - new DataEntity("test_key_2", "test_value_2"), - new DataEntity("test_key_3", "test_value_3") - }; - - /** Amount of data we want to round trip in this test */ - private static final int TEST_DATA_SIZE = 1024 * 1024; // 1MB - - /** Buffer size used when reading data from the restore task */ - private static final int READ_BUFFER_SIZE = 1024; // 1024 byte buffer. - - /** Key parameters used for the secondary encryption key */ - private static final String KEY_ALGORITHM = "AES"; - - private static final int KEY_SIZE_BITS = 256; - - /** Package name for our test package */ - private static final String TEST_PACKAGE_NAME = "com.android.backup.test"; - - /** The name we use to refer to our secondary key */ - private static final String TEST_KEY_ALIAS = "test/backup/KEY_ALIAS"; - - /** Original data used for comparison after round trip */ - private final byte[] mOriginalData = new byte[TEST_DATA_SIZE]; - - /** App context, used to store the key data and chunk listings */ - private Context mContext; - - /** The secondary key we're using for the test */ - private RecoverableKeyStoreSecondaryKey mSecondaryKey; - - /** Source of random material which is considered non-predictable in its' generation */ - private final SecureRandom mSecureRandom = new SecureRandom(); - - private RecoverableKeyStoreSecondaryKeyManager.RecoverableKeyStoreSecondaryKeyManagerProvider - mSecondaryKeyManagerProvider; - private DummyServer mDummyServer; - private RecoveryController mRecoveryController; - - @Mock private ParcelFileDescriptor mParcelFileDescriptor; - - @Before - public void setUp() throws NoSuchAlgorithmException, InternalRecoveryServiceException { - MockitoAnnotations.initMocks(this); - - ShadowBackupDataInput.reset(); - ShadowBackupDataOutput.reset(); - - mContext = ApplicationProvider.getApplicationContext(); - mSecondaryKey = new RecoverableKeyStoreSecondaryKey(TEST_KEY_ALIAS, generateAesKey()); - mDummyServer = new DummyServer(); - mSecondaryKeyManagerProvider = - () -> - new RecoverableKeyStoreSecondaryKeyManager( - RecoveryController.getInstance(mContext), mSecureRandom); - - fillBuffer(mOriginalData); - } - - @Test - public void testFull_nonIncrementalBackupAndRestoreAreSuccessful() throws Exception { - byte[] backupData = performFullBackup(mOriginalData); - assertThat(backupData).isNotEqualTo(mOriginalData); - byte[] restoredData = performFullRestore(backupData); - assertThat(restoredData).isEqualTo(mOriginalData); - } - - @Test - public void testKeyValue_nonIncrementalBackupAndRestoreAreSuccessful() throws Exception { - byte[] backupData = performNonIncrementalKeyValueBackup(KEY_VALUE_DATA); - - // Get the secondary key used to do backup. - Optional<RecoverableKeyStoreSecondaryKey> secondaryKey = - mSecondaryKeyManagerProvider.get().get(mDummyServer.mSecondaryKeyAlias); - assertThat(secondaryKey.isPresent()).isTrue(); - - Set<DataEntity> restoredData = performKeyValueRestore(backupData, secondaryKey.get()); - - assertThat(restoredData).containsExactly(KEY_VALUE_DATA).inOrder(); - } - - /** Perform a key/value backup and return the backed-up representation of the data */ - private byte[] performNonIncrementalKeyValueBackup(DataEntity[] backupData) - throws Exception { - // Populate test key/value data. - for (DataEntity entity : backupData) { - ShadowBackupDataInput.addEntity(entity); - } - - EncryptedKvBackupTask.EncryptedKvBackupTaskFactory backupTaskFactory = - new EncryptedKvBackupTask.EncryptedKvBackupTaskFactory(); - EncryptedKvBackupTask backupTask = - backupTaskFactory.newInstance( - mContext, - mSecureRandom, - mDummyServer, - CryptoSettings.getInstance(mContext), - mSecondaryKeyManagerProvider, - mParcelFileDescriptor, - TEST_PACKAGE_NAME); - - backupTask.performBackup(/* incremental */ false); - - return mDummyServer.mStoredData; - } - - /** Perform a full backup and return the backed-up representation of the data */ - private byte[] performFullBackup(byte[] backupData) throws Exception { - DummyServer dummyServer = new DummyServer(); - EncryptedFullBackupTask backupTask = - EncryptedFullBackupTask.newInstance( - mContext, - dummyServer, - mSecureRandom, - mSecondaryKey, - TEST_PACKAGE_NAME, - new ByteArrayInputStream(backupData)); - backupTask.call(); - return dummyServer.mStoredData; - } - - private Set<DataEntity> performKeyValueRestore( - byte[] backupData, RecoverableKeyStoreSecondaryKey secondaryKey) throws Exception { - EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory restoreTaskFactory = - new EncryptedKvRestoreTask.EncryptedKvRestoreTaskFactory(); - EncryptedKvRestoreTask restoreTask = - restoreTaskFactory.newInstance( - mContext, - mSecondaryKeyManagerProvider, - new FakeFullRestoreDownloader(backupData), - secondaryKey.getAlias(), - KeyWrapUtils.wrap( - secondaryKey.getSecretKey(), getTertiaryKey(secondaryKey))); - restoreTask.getRestoreData(mParcelFileDescriptor); - return ShadowBackupDataOutput.getEntities(); - } - - /** Perform a full restore and return the bytes obtained from the restore process */ - private byte[] performFullRestore(byte[] backupData) - throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, - InvalidAlgorithmParameterException, InvalidKeyException, - IllegalBlockSizeException { - ByteArrayOutputStream decryptedOutput = new ByteArrayOutputStream(); - - EncryptedFullRestoreTask restoreTask = - EncryptedFullRestoreTask.newInstance( - mContext, - new FakeFullRestoreDownloader(backupData), - getTertiaryKey(mSecondaryKey)); - - byte[] buffer = new byte[READ_BUFFER_SIZE]; - int bytesRead = restoreTask.readNextChunk(buffer); - while (bytesRead != -1) { - decryptedOutput.write(buffer, 0, bytesRead); - bytesRead = restoreTask.readNextChunk(buffer); - } - - return decryptedOutput.toByteArray(); - } - - /** Get the tertiary key for our test package from the key manager */ - private SecretKey getTertiaryKey(RecoverableKeyStoreSecondaryKey secondaryKey) - throws IllegalBlockSizeException, InvalidAlgorithmParameterException, - NoSuchAlgorithmException, IOException, NoSuchPaddingException, - InvalidKeyException { - TertiaryKeyManager tertiaryKeyManager = - new TertiaryKeyManager( - mContext, - mSecureRandom, - TertiaryKeyRotationScheduler.getInstance(mContext), - secondaryKey, - TEST_PACKAGE_NAME); - return tertiaryKeyManager.getKey(); - } - - /** Fill a buffer with data in a predictable way */ - private void fillBuffer(byte[] buffer) { - byte loopingCounter = 0; - for (int i = 0; i < buffer.length; i++) { - buffer[i] = loopingCounter; - loopingCounter++; - } - } - - /** Generate a new, random, AES key */ - public static SecretKey generateAesKey() throws NoSuchAlgorithmException { - KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); - keyGenerator.init(KEY_SIZE_BITS); - return keyGenerator.generateKey(); - } - - /** - * Dummy backup data endpoint. This stores the data so we can use it in subsequent test steps. - */ - private static class DummyServer implements CryptoBackupServer { - private static final String DUMMY_DOC_ID = "DummyDoc"; - - byte[] mStoredData = null; - String mSecondaryKeyAlias; - - @Override - public String uploadIncrementalBackup( - String packageName, - String oldDocId, - byte[] diffScript, - WrappedKeyProto.WrappedKey tertiaryKey) { - throw new RuntimeException("Not Implemented"); - } - - @Override - public String uploadNonIncrementalBackup( - String packageName, byte[] data, WrappedKeyProto.WrappedKey tertiaryKey) { - assertThat(packageName).isEqualTo(TEST_PACKAGE_NAME); - mStoredData = data; - return DUMMY_DOC_ID; - } - - @Override - public void setActiveSecondaryKeyAlias( - String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) { - mSecondaryKeyAlias = keyAlias; - } - } - - /** Fake package wrapper which returns data from a byte array. */ - private static class FakeFullRestoreDownloader extends FullRestoreDownloader { - private final ByteArrayInputStream mData; - - FakeFullRestoreDownloader(byte[] data) { - // We override all methods of the superclass, so it does not require any collaborators. - super(); - mData = new ByteArrayInputStream(data); - } - - @Override - public int readNextChunk(byte[] buffer) throws IOException { - return mData.read(buffer); - } - - @Override - public void finish(FinishType finishType) { - // Do nothing. - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/Android.bp b/packages/BackupEncryption/test/robolectric/Android.bp deleted file mode 100644 index 7665d8f4e4c2..000000000000 --- a/packages/BackupEncryption/test/robolectric/Android.bp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2018 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 { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -android_robolectric_test { - name: "BackupEncryptionRoboTests", - srcs: [ - "src/**/*.java", -// ":FrameworksServicesRoboShadows", - ], - java_resource_dirs: ["config"], - libs: [ - "backup-encryption-protos", - "platform-test-annotations", - "testng", - "truth-prebuilt", - ], - static_libs: [ - "androidx.test.core", - "androidx.test.runner", - "androidx.test.rules", - ], - instrumentation_for: "BackupEncryption", -} - -filegroup { - name: "BackupEncryptionRoboShadows", - srcs: ["src/com/android/server/testing/shadows/**/*.java"], -} diff --git a/packages/BackupEncryption/test/robolectric/AndroidManifest.xml b/packages/BackupEncryption/test/robolectric/AndroidManifest.xml deleted file mode 100644 index ae5cdd918abd..000000000000 --- a/packages/BackupEncryption/test/robolectric/AndroidManifest.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?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" - coreApp="true" - package="com.android.server.backup.encryption.robotests"> - - <application/> - -</manifest> diff --git a/packages/BackupEncryption/test/robolectric/config/robolectric.properties b/packages/BackupEncryption/test/robolectric/config/robolectric.properties deleted file mode 100644 index 26fceb3f84a4..000000000000 --- a/packages/BackupEncryption/test/robolectric/config/robolectric.properties +++ /dev/null @@ -1,17 +0,0 @@ -# -# 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. -# - -sdk=NEWEST_SDK diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java deleted file mode 100644 index 979b3d5dc13a..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/CryptoSettingsTest.java +++ /dev/null @@ -1,212 +0,0 @@ -/* - * 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.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.app.Application; -import android.security.keystore.recovery.RecoveryController; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.server.testing.shadows.ShadowRecoveryController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.util.Optional; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowRecoveryController.class) -public class CryptoSettingsTest { - - private static final String TEST_KEY_ALIAS = - "com.android.server.backup.encryption/keystore/08120c326b928ff34c73b9c58581da63"; - - private CryptoSettings mCryptoSettings; - private Application mApplication; - - @Before - public void setUp() { - ShadowRecoveryController.reset(); - - mApplication = ApplicationProvider.getApplicationContext(); - mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication); - } - - @Test - public void getActiveSecondaryAlias_isInitiallyAbsent() { - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse(); - } - - @Test - public void getActiveSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception { - setAliasIsInRecoveryController(TEST_KEY_ALIAS); - mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS); - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); - } - - @Test - public void getNextSecondaryAlias_isInitiallyAbsent() { - assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); - } - - @Test - public void getNextSecondaryAlias_returnsAliasIfKeyIsInRecoveryController() throws Exception { - setAliasIsInRecoveryController(TEST_KEY_ALIAS); - mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS); - assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); - } - - @Test - public void isInitialized_isInitiallyFalse() { - assertThat(mCryptoSettings.getIsInitialized()).isFalse(); - } - - @Test - public void setActiveSecondaryAlias_throwsIfKeyIsNotInRecoveryController() { - assertThrows( - IllegalArgumentException.class, - () -> mCryptoSettings.setActiveSecondaryKeyAlias(TEST_KEY_ALIAS)); - } - - @Test - public void setNextSecondaryAlias_inRecoveryController_setsAlias() throws Exception { - setAliasIsInRecoveryController(TEST_KEY_ALIAS); - - mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS); - - assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); - } - - @Test - public void setNextSecondaryAlias_throwsIfKeyIsNotInRecoveryController() { - assertThrows( - IllegalArgumentException.class, - () -> mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS)); - } - - @Test - public void removeNextSecondaryAlias_removesIt() throws Exception { - setAliasIsInRecoveryController(TEST_KEY_ALIAS); - mCryptoSettings.setNextSecondaryAlias(TEST_KEY_ALIAS); - - mCryptoSettings.removeNextSecondaryKeyAlias(); - - assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); - } - - @Test - public void initializeWithKeyAlias_setsAsInitialized() throws Exception { - setAliasIsInRecoveryController(TEST_KEY_ALIAS); - mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS); - assertThat(mCryptoSettings.getIsInitialized()).isTrue(); - } - - @Test - public void initializeWithKeyAlias_setsActiveAlias() throws Exception { - setAliasIsInRecoveryController(TEST_KEY_ALIAS); - mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS); - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(TEST_KEY_ALIAS); - } - - @Test - public void initializeWithKeyAlias_throwsIfKeyIsNotInRecoveryController() { - assertThrows( - IllegalArgumentException.class, - () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS)); - } - - @Test - public void initializeWithKeyAlias_throwsIfAlreadyInitialized() throws Exception { - setAliasIsInRecoveryController(TEST_KEY_ALIAS); - mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS); - - assertThrows( - IllegalStateException.class, - () -> mCryptoSettings.initializeWithKeyAlias(TEST_KEY_ALIAS)); - } - - @Test - public void getSecondaryLastRotated_returnsEmptyInitially() { - assertThat(mCryptoSettings.getSecondaryLastRotated()).isEqualTo(Optional.empty()); - } - - @Test - public void getSecondaryLastRotated_returnsTimestampAfterItIsSet() { - long timestamp = 1000001; - - mCryptoSettings.setSecondaryLastRotated(timestamp); - - assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(timestamp); - } - - @Test - public void getAncestralSecondaryKeyVersion_notSet_returnsOptionalAbsent() { - assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().isPresent()).isFalse(); - } - - @Test - public void getAncestralSecondaryKeyVersion_isSet_returnsSetValue() { - String secondaryKeyVersion = "some_secondary_key"; - mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion); - - assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()) - .isEqualTo(secondaryKeyVersion); - } - - @Test - public void getAncestralSecondaryKeyVersion_isSetMultipleTimes_returnsLastSetValue() { - String secondaryKeyVersion1 = "some_secondary_key"; - String secondaryKeyVersion2 = "another_secondary_key"; - mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion1); - mCryptoSettings.setAncestralSecondaryKeyVersion(secondaryKeyVersion2); - - assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()) - .isEqualTo(secondaryKeyVersion2); - } - - @Test - public void clearAllSettingsForBackup_clearsStateForBackup() throws Exception { - String key1 = "key1"; - String key2 = "key2"; - String ancestralKey = "ancestral_key"; - setAliasIsInRecoveryController(key1); - setAliasIsInRecoveryController(key2); - mCryptoSettings.setActiveSecondaryKeyAlias(key1); - mCryptoSettings.setNextSecondaryAlias(key2); - mCryptoSettings.setSecondaryLastRotated(100001); - mCryptoSettings.setAncestralSecondaryKeyVersion(ancestralKey); - - mCryptoSettings.clearAllSettingsForBackup(); - - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()).isFalse(); - assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); - assertThat(mCryptoSettings.getSecondaryLastRotated().isPresent()).isFalse(); - assertThat(mCryptoSettings.getAncestralSecondaryKeyVersion().get()).isEqualTo(ancestralKey); - } - - private void setAliasIsInRecoveryController(String alias) throws Exception { - RecoveryController recoveryController = RecoveryController.getInstance(mApplication); - recoveryController.generateKey(alias); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/StreamUtilsTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/StreamUtilsTest.java deleted file mode 100644 index a95e87e3a8b7..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/StreamUtilsTest.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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.google.common.truth.Truth.assertThat; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -@RunWith(RobolectricTestRunner.class) -public class StreamUtilsTest { - private static final int SOURCE_DATA_SIZE = 64; - - private byte[] mSourceData; - - private InputStream mSource; - private ByteArrayOutputStream mDestination; - - @Before - public void setUp() { - mSourceData = new byte[SOURCE_DATA_SIZE]; - for (byte i = 0; i < SOURCE_DATA_SIZE; i++) { - mSourceData[i] = i; - } - mSource = new ByteArrayInputStream(mSourceData); - mDestination = new ByteArrayOutputStream(); - } - - @Test - public void copyStream_copiesAllBytesIfAsked() throws IOException { - StreamUtils.copyStream(mSource, mDestination, mSourceData.length); - assertOutputHasBytes(mSourceData.length); - } - - @Test - public void copyStream_stopsShortIfAsked() throws IOException { - StreamUtils.copyStream(mSource, mDestination, mSourceData.length - 10); - assertOutputHasBytes(mSourceData.length - 10); - } - - @Test - public void copyStream_stopsShortIfAskedToCopyMoreThanAvailable() throws IOException { - StreamUtils.copyStream(mSource, mDestination, mSourceData.length + 10); - assertOutputHasBytes(mSourceData.length); - } - - private void assertOutputHasBytes(int count) { - byte[] output = mDestination.toByteArray(); - assertThat(output.length).isEqualTo(count); - for (int i = 0; i < count; i++) { - assertThat(output[i]).isEqualTo(mSourceData[i]); - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java deleted file mode 100644 index c12464c50175..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkHashTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2018 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.chunk; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import com.google.common.primitives.Bytes; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.util.Arrays; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class ChunkHashTest { - private static final int HASH_LENGTH_BYTES = 256 / 8; - private static final byte[] TEST_HASH_1 = Arrays.copyOf(new byte[] {1}, HASH_LENGTH_BYTES); - private static final byte[] TEST_HASH_2 = Arrays.copyOf(new byte[] {2}, HASH_LENGTH_BYTES); - - @Test - public void testGetHash_returnsHash() { - ChunkHash chunkHash = new ChunkHash(TEST_HASH_1); - - byte[] hash = chunkHash.getHash(); - - assertThat(hash).asList().containsExactlyElementsIn(Bytes.asList(TEST_HASH_1)).inOrder(); - } - - @Test - public void testEquals() { - ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1); - ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1); - ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2); - - assertThat(chunkHash1).isEqualTo(equalChunkHash1); - assertThat(chunkHash1).isNotEqualTo(chunkHash2); - } - - @Test - public void testHashCode() { - ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1); - ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1); - ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2); - - int hash1 = chunkHash1.hashCode(); - int equalHash1 = equalChunkHash1.hashCode(); - int hash2 = chunkHash2.hashCode(); - - assertThat(hash1).isEqualTo(equalHash1); - assertThat(hash1).isNotEqualTo(hash2); - } - - @Test - public void testCompareTo_whenEqual_returnsZero() { - ChunkHash chunkHash = new ChunkHash(TEST_HASH_1); - ChunkHash equalChunkHash = new ChunkHash(TEST_HASH_1); - - int result = chunkHash.compareTo(equalChunkHash); - - assertThat(result).isEqualTo(0); - } - - @Test - public void testCompareTo_whenArgumentGreater_returnsNegative() { - ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1); - ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2); - - int result = chunkHash1.compareTo(chunkHash2); - - assertThat(result).isLessThan(0); - } - - @Test - public void testCompareTo_whenArgumentSmaller_returnsPositive() { - ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1); - ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2); - - int result = chunkHash2.compareTo(chunkHash1); - - assertThat(result).isGreaterThan(0); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java deleted file mode 100644 index c5f78c254cd1..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/ChunkListingMapTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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.chunk; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; - -import com.google.common.base.Charsets; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.util.Arrays; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class ChunkListingMapTest { - private static final ChunkHash CHUNK_A_HASH = getHash("CHUNK_A"); - private static final ChunkHash CHUNK_B_HASH = getHash("CHUNK_B"); - private static final ChunkHash CHUNK_C_HASH = getHash("CHUNK_C"); - - private static final int CHUNK_A_LENGTH = 256; - private static final int CHUNK_B_LENGTH = 1024; - private static final int CHUNK_C_LENGTH = 4055; - - private static final int CHUNK_A_START = 0; - private static final int CHUNK_B_START = CHUNK_A_START + CHUNK_A_LENGTH; - private static final int CHUNK_C_START = CHUNK_B_START + CHUNK_B_LENGTH; - - private ChunkListingMap mChunkListingMap; - - @Before - public void setUp() { - mChunkListingMap = createFromFixture(); - } - - @Test - public void hasChunk_isTrueForExistingChunks() { - assertThat(mChunkListingMap.hasChunk(CHUNK_A_HASH)).isTrue(); - assertThat(mChunkListingMap.hasChunk(CHUNK_B_HASH)).isTrue(); - assertThat(mChunkListingMap.hasChunk(CHUNK_C_HASH)).isTrue(); - } - - @Test - public void hasChunk_isFalseForNonexistentChunks() { - assertThat(mChunkListingMap.hasChunk(getHash("CHUNK_D"))).isFalse(); - assertThat(mChunkListingMap.hasChunk(getHash(""))).isFalse(); - } - - @Test - public void getChunkListing_hasCorrectLengths() { - assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getLength()) - .isEqualTo(CHUNK_A_LENGTH); - assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getLength()) - .isEqualTo(CHUNK_B_LENGTH); - assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).getLength()) - .isEqualTo(CHUNK_C_LENGTH); - } - - @Test - public void getChunkListing_hasCorrectStarts() { - assertThat(mChunkListingMap.getChunkEntry(CHUNK_A_HASH).getStart()) - .isEqualTo(CHUNK_A_START); - assertThat(mChunkListingMap.getChunkEntry(CHUNK_B_HASH).getStart()) - .isEqualTo(CHUNK_B_START); - assertThat(mChunkListingMap.getChunkEntry(CHUNK_C_HASH).getStart()) - .isEqualTo(CHUNK_C_START); - } - - @Test - public void getChunkListing_isNullForNonExistentChunks() { - assertThat(mChunkListingMap.getChunkEntry(getHash("Hey"))).isNull(); - } - - private static ChunkListingMap createFromFixture() { - ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing(); - chunkListing.chunks = new ChunksMetadataProto.Chunk[3]; - chunkListing.chunks[0] = newChunk(CHUNK_A_HASH.getHash(), CHUNK_A_LENGTH); - chunkListing.chunks[1] = newChunk(CHUNK_B_HASH.getHash(), CHUNK_B_LENGTH); - chunkListing.chunks[2] = newChunk(CHUNK_C_HASH.getHash(), CHUNK_C_LENGTH); - return ChunkListingMap.fromProto(chunkListing); - } - - private static ChunkHash getHash(String name) { - return new ChunkHash( - Arrays.copyOf(name.getBytes(Charsets.UTF_8), ChunkHash.HASH_LENGTH_BYTES)); - } - - public static ChunksMetadataProto.Chunk newChunk(byte[] hash, int length) { - ChunksMetadataProto.Chunk newChunk = new ChunksMetadataProto.Chunk(); - newChunk.hash = Arrays.copyOf(hash, hash.length); - newChunk.length = length; - return newChunk; - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java deleted file mode 100644 index c6b29b7b7236..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunk/EncryptedChunkOrderingTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2018 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.chunk; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import com.google.common.primitives.Bytes; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class EncryptedChunkOrderingTest { - private static final byte[] TEST_BYTE_ARRAY_1 = new byte[] {1, 2, 3, 4, 5}; - private static final byte[] TEST_BYTE_ARRAY_2 = new byte[] {5, 4, 3, 2, 1}; - - @Test - public void testEncryptedChunkOrdering_returnsValue() { - EncryptedChunkOrdering encryptedChunkOrdering = - EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1); - - byte[] bytes = encryptedChunkOrdering.encryptedChunkOrdering(); - - assertThat(bytes) - .asList() - .containsExactlyElementsIn(Bytes.asList(TEST_BYTE_ARRAY_1)) - .inOrder(); - } - - @Test - public void testEquals() { - EncryptedChunkOrdering chunkOrdering1 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1); - EncryptedChunkOrdering equalChunkOrdering1 = - EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1); - EncryptedChunkOrdering chunkOrdering2 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_2); - - assertThat(chunkOrdering1).isEqualTo(equalChunkOrdering1); - assertThat(chunkOrdering1).isNotEqualTo(chunkOrdering2); - } - - @Test - public void testHashCode() { - EncryptedChunkOrdering chunkOrdering1 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1); - EncryptedChunkOrdering equalChunkOrdering1 = - EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_1); - EncryptedChunkOrdering chunkOrdering2 = EncryptedChunkOrdering.create(TEST_BYTE_ARRAY_2); - - int hash1 = chunkOrdering1.hashCode(); - int equalHash1 = equalChunkOrdering1.hashCode(); - int hash2 = chunkOrdering2.hashCode(); - - assertThat(hash1).isEqualTo(equalHash1); - assertThat(hash1).isNotEqualTo(hash2); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/BackupFileBuilderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/BackupFileBuilderTest.java deleted file mode 100644 index 590938efe148..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/BackupFileBuilderTest.java +++ /dev/null @@ -1,614 +0,0 @@ -/* - * 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.chunking; - -import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.AES_256_GCM; -import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED; -import static com.android.server.backup.testing.CryptoTestUtils.newChunk; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import static junit.framework.Assert.fail; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.testng.Assert.assertThrows; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.testing.DiffScriptProcessor; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.io.Files; -import com.google.common.primitives.Bytes; -import com.google.common.primitives.Longs; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class BackupFileBuilderTest { - private static final String TEST_DATA_1 = - "I'm already there or close to [T7-9/executive level] in terms of big-picture vision"; - private static final String TEST_DATA_2 = - "I was known for Real Games and should have been brought in for advice"; - private static final String TEST_DATA_3 = - "Pride is rooted in the delusional belief held by all humans in an unchanging self"; - - private static final byte[] TEST_FINGERPRINT_MIXER_SALT = - Arrays.copyOf(new byte[] {22}, ChunkHash.HASH_LENGTH_BYTES); - - private static final ChunkHash TEST_HASH_1 = - new ChunkHash(Arrays.copyOf(new byte[] {0}, EncryptedChunk.KEY_LENGTH_BYTES)); - private static final ChunkHash TEST_HASH_2 = - new ChunkHash(Arrays.copyOf(new byte[] {1}, EncryptedChunk.KEY_LENGTH_BYTES)); - private static final ChunkHash TEST_HASH_3 = - new ChunkHash(Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES)); - - private static final byte[] TEST_NONCE = - Arrays.copyOf(new byte[] {3}, EncryptedChunk.NONCE_LENGTH_BYTES); - - private static final EncryptedChunk TEST_CHUNK_1 = - EncryptedChunk.create(TEST_HASH_1, TEST_NONCE, TEST_DATA_1.getBytes(UTF_8)); - private static final EncryptedChunk TEST_CHUNK_2 = - EncryptedChunk.create(TEST_HASH_2, TEST_NONCE, TEST_DATA_2.getBytes(UTF_8)); - private static final EncryptedChunk TEST_CHUNK_3 = - EncryptedChunk.create(TEST_HASH_3, TEST_NONCE, TEST_DATA_3.getBytes(UTF_8)); - - private static final byte[] TEST_CHECKSUM = {1, 2, 3, 4, 5, 6}; - - @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - - private File mOldFile; - private ChunksMetadataProto.ChunkListing mOldChunkListing; - private EncryptedChunkEncoder mEncryptedChunkEncoder; - - @Before - public void setUp() { - mEncryptedChunkEncoder = new LengthlessEncryptedChunkEncoder(); - } - - @Test - public void writeChunks_nonIncremental_writesCorrectRawData() throws Exception { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - BackupFileBuilder backupFileBuilder = BackupFileBuilder.createForNonIncremental(output); - - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2), - getNewChunkMap(TEST_HASH_1, TEST_HASH_2)); - - byte[] actual = output.toByteArray(); - byte[] expected = - Bytes.concat( - TEST_CHUNK_1.nonce(), - TEST_CHUNK_1.encryptedBytes(), - TEST_CHUNK_2.nonce(), - TEST_CHUNK_2.encryptedBytes()); - assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder(); - } - - @Test - public void writeChunks_nonIncrementalWithDuplicates_writesEachChunkOnlyOnce() - throws Exception { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - BackupFileBuilder backupFileBuilder = BackupFileBuilder.createForNonIncremental(output); - - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_1), - getNewChunkMap(TEST_HASH_1, TEST_HASH_2)); - - byte[] actual = output.toByteArray(); - byte[] expected = - Bytes.concat( - TEST_CHUNK_1.nonce(), - TEST_CHUNK_1.encryptedBytes(), - TEST_CHUNK_2.nonce(), - TEST_CHUNK_2.encryptedBytes()); - assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder(); - } - - @Test - public void writeChunks_incremental_writesParsableDiffScript() throws Exception { - // We will insert chunk 2 in between chunks 1 and 3. - setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_3)); - ByteArrayOutputStream diffOutputStream = new ByteArrayOutputStream(); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental(diffOutputStream, mOldChunkListing); - - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_3), - getNewChunkMap(TEST_HASH_2)); - backupFileBuilder.finish(getTestMetadata()); - - byte[] actual = - stripMetadataAndPositionFromOutput(parseDiffScript(diffOutputStream.toByteArray())); - byte[] expected = - Bytes.concat( - TEST_CHUNK_1.nonce(), - TEST_CHUNK_1.encryptedBytes(), - TEST_CHUNK_2.nonce(), - TEST_CHUNK_2.encryptedBytes(), - TEST_CHUNK_3.nonce(), - TEST_CHUNK_3.encryptedBytes()); - assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder(); - } - - @Test - public void writeChunks_incrementalWithDuplicates_writesEachChunkOnlyOnce() throws Exception { - // We will insert chunk 2 twice in between chunks 1 and 3. - setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_3)); - ByteArrayOutputStream diffOutputStream = new ByteArrayOutputStream(); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental(diffOutputStream, mOldChunkListing); - - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_2, TEST_HASH_3), - getNewChunkMap(TEST_HASH_2)); - backupFileBuilder.finish(getTestMetadata()); - - byte[] actual = - stripMetadataAndPositionFromOutput(parseDiffScript(diffOutputStream.toByteArray())); - byte[] expected = - Bytes.concat( - TEST_CHUNK_1.nonce(), - TEST_CHUNK_1.encryptedBytes(), - TEST_CHUNK_2.nonce(), - TEST_CHUNK_2.encryptedBytes(), - TEST_CHUNK_3.nonce(), - TEST_CHUNK_3.encryptedBytes()); - assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder(); - } - - @Test - public void writeChunks_writesChunksInOrderOfHash() throws Exception { - setUpOldBackupWithChunks(ImmutableList.of()); - ByteArrayOutputStream diffOutputStream = new ByteArrayOutputStream(); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental(diffOutputStream, mOldChunkListing); - - // Write chunks out of order. - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_2, TEST_HASH_1), - getNewChunkMap(TEST_HASH_2, TEST_HASH_1)); - backupFileBuilder.finish(getTestMetadata()); - - byte[] actual = - stripMetadataAndPositionFromOutput(parseDiffScript(diffOutputStream.toByteArray())); - byte[] expected = - Bytes.concat( - TEST_CHUNK_1.nonce(), - TEST_CHUNK_1.encryptedBytes(), - TEST_CHUNK_2.nonce(), - TEST_CHUNK_2.encryptedBytes()); - assertThat(actual).asList().containsExactlyElementsIn(Bytes.asList(expected)).inOrder(); - } - - @Test - public void writeChunks_alreadyFlushed_throwsException() throws Exception { - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), new ChunksMetadataProto.ChunkListing()); - backupFileBuilder.finish(getTestMetadata()); - - assertThrows( - IllegalStateException.class, - () -> backupFileBuilder.writeChunks(ImmutableList.of(), getNewChunkMap())); - } - - @Test - public void getNewChunkListing_hasChunksInOrderOfKey() throws Exception { - // We will insert chunk 2 in between chunks 1 and 3. - setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_3)); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), mOldChunkListing); - - // Write chunks out of order. - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_3, TEST_HASH_2), - getNewChunkMap(TEST_HASH_2)); - backupFileBuilder.finish(getTestMetadata()); - - ChunksMetadataProto.ChunkListing expected = expectedChunkListing(); - ChunksMetadataProto.ChunkListing actual = - backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT); - assertListingsEqual(actual, expected); - } - - @Test - public void getNewChunkListing_writeChunksInTwoBatches_returnsListingContainingAllChunks() - throws Exception { - // We will insert chunk 2 in between chunks 1 and 3. - setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_3)); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), mOldChunkListing); - - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2), getNewChunkMap(TEST_HASH_2)); - backupFileBuilder.writeChunks(ImmutableList.of(TEST_HASH_3), getNewChunkMap(TEST_HASH_2)); - backupFileBuilder.finish(getTestMetadata()); - - ChunksMetadataProto.ChunkListing expected = expectedChunkListing(); - ChunksMetadataProto.ChunkListing actual = - backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT); - assertListingsEqual(actual, expected); - } - - @Test - public void getNewChunkListing_writeDuplicateChunks_writesEachChunkOnlyOnce() throws Exception { - // We will append [2][3][3][2] onto [1]. - setUpOldBackupWithChunks(ImmutableList.of(TEST_CHUNK_1)); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), mOldChunkListing); - - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_3), - getNewChunkMap(TEST_HASH_3, TEST_HASH_2)); - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_3, TEST_HASH_2), - getNewChunkMap(TEST_HASH_3, TEST_HASH_2)); - backupFileBuilder.finish(getTestMetadata()); - - ChunksMetadataProto.ChunkListing expected = expectedChunkListing(); - ChunksMetadataProto.ChunkListing actual = - backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT); - assertListingsEqual(actual, expected); - } - - @Test - public void getNewChunkListing_nonIncrementalWithNoSalt_doesNotThrowOnSerialisation() { - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForNonIncremental(new ByteArrayOutputStream()); - - ChunksMetadataProto.ChunkListing newChunkListing = - backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null); - - // Does not throw. - ChunksMetadataProto.ChunkListing.toByteArray(newChunkListing); - } - - @Test - public void getNewChunkListing_incrementalWithNoSalt_doesNotThrowOnSerialisation() - throws Exception { - - setUpOldBackupWithChunks(ImmutableList.of()); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), mOldChunkListing); - - ChunksMetadataProto.ChunkListing newChunkListing = - backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null); - - // Does not throw. - ChunksMetadataProto.ChunkListing.toByteArray(newChunkListing); - } - - @Test - public void getNewChunkListing_nonIncrementalWithNoSalt_hasEmptySalt() { - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForNonIncremental(new ByteArrayOutputStream()); - - ChunksMetadataProto.ChunkListing newChunkListing = - backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null); - - assertThat(newChunkListing.fingerprintMixerSalt).isEmpty(); - } - - @Test - public void getNewChunkListing_incrementalWithNoSalt_hasEmptySalt() throws Exception { - setUpOldBackupWithChunks(ImmutableList.of()); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), mOldChunkListing); - - ChunksMetadataProto.ChunkListing newChunkListing = - backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null); - - assertThat(newChunkListing.fingerprintMixerSalt).isEmpty(); - } - - @Test - public void getNewChunkListing_nonIncrementalWithSalt_hasGivenSalt() { - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForNonIncremental(new ByteArrayOutputStream()); - - ChunksMetadataProto.ChunkListing newChunkListing = - backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT); - - assertThat(newChunkListing.fingerprintMixerSalt).isEqualTo(TEST_FINGERPRINT_MIXER_SALT); - } - - @Test - public void getNewChunkListing_incrementalWithSalt_hasGivenSalt() throws Exception { - setUpOldBackupWithChunks(ImmutableList.of()); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), mOldChunkListing); - - ChunksMetadataProto.ChunkListing newChunkListing = - backupFileBuilder.getNewChunkListing(TEST_FINGERPRINT_MIXER_SALT); - - assertThat(newChunkListing.fingerprintMixerSalt).isEqualTo(TEST_FINGERPRINT_MIXER_SALT); - } - - @Test - public void getNewChunkListing_nonIncremental_hasCorrectCipherTypeAndChunkOrderingType() { - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForNonIncremental(new ByteArrayOutputStream()); - - ChunksMetadataProto.ChunkListing newChunkListing = - backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null); - - assertThat(newChunkListing.cipherType).isEqualTo(ChunksMetadataProto.AES_256_GCM); - assertThat(newChunkListing.chunkOrderingType) - .isEqualTo(ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED); - } - - @Test - public void getNewChunkListing_incremental_hasCorrectCipherTypeAndChunkOrderingType() - throws Exception { - setUpOldBackupWithChunks(ImmutableList.of()); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), mOldChunkListing); - - ChunksMetadataProto.ChunkListing newChunkListing = - backupFileBuilder.getNewChunkListing(/*fingerprintMixerSalt=*/ null); - - assertThat(newChunkListing.cipherType).isEqualTo(ChunksMetadataProto.AES_256_GCM); - assertThat(newChunkListing.chunkOrderingType) - .isEqualTo(ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED); - } - - @Test - public void getNewChunkOrdering_chunksHaveCorrectStartPositions() throws Exception { - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), new ChunksMetadataProto.ChunkListing()); - - // Write out of order by key to check that ordering is maintained. - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_3, TEST_HASH_2), - getNewChunkMap(TEST_HASH_1, TEST_HASH_3, TEST_HASH_2)); - backupFileBuilder.finish(getTestMetadata()); - - ChunksMetadataProto.ChunkOrdering actual = - backupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM); - // The chunks are listed in the order they are written above, but the start positions are - // determined by the order in the encrypted blob (which is lexicographical by key). - int chunk1Start = 0; - int chunk2Start = - chunk1Start + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_1); - int chunk3Start = - chunk2Start + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_2); - - int[] expected = {chunk1Start, chunk3Start, chunk2Start}; - assertThat(actual.starts.length).isEqualTo(expected.length); - for (int i = 0; i < actual.starts.length; i++) { - assertThat(expected[i]).isEqualTo(actual.starts[i]); - } - } - - @Test - public void getNewChunkOrdering_duplicateChunks_writesDuplicates() throws Exception { - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), new ChunksMetadataProto.ChunkListing()); - - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_2), - getNewChunkMap(TEST_HASH_1, TEST_HASH_2)); - backupFileBuilder.writeChunks( - ImmutableList.of(TEST_HASH_3, TEST_HASH_3), getNewChunkMap(TEST_HASH_3)); - backupFileBuilder.finish(getTestMetadata()); - - ChunksMetadataProto.ChunkOrdering actual = - backupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM); - int chunk1Start = 0; - int chunk2Start = - chunk1Start + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_1); - int chunk3Start = - chunk2Start + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_2); - - int[] expected = {chunk1Start, chunk2Start, chunk2Start, chunk3Start, chunk3Start}; - assertThat(actual.starts.length).isEqualTo(expected.length); - for (int i = 0; i < actual.starts.length; i++) { - assertThat(expected[i]).isEqualTo(actual.starts[i]); - } - } - - @Test - public void getNewChunkOrdering_returnsOrderingWithChecksum() throws Exception { - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - new ByteArrayOutputStream(), new ChunksMetadataProto.ChunkListing()); - - backupFileBuilder.writeChunks(ImmutableList.of(TEST_HASH_1), getNewChunkMap(TEST_HASH_1)); - backupFileBuilder.finish(getTestMetadata()); - - ChunksMetadataProto.ChunkOrdering actual = - backupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM); - assertThat(actual.checksum).isEqualTo(TEST_CHECKSUM); - } - - @Test - public void finish_writesMetadata() throws Exception { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - BackupFileBuilder builder = BackupFileBuilder.createForNonIncremental(output); - ChunksMetadataProto.ChunksMetadata expectedMetadata = getTestMetadata(); - - builder.finish(expectedMetadata); - - // The output is [metadata]+[long giving size of metadata]. - byte[] metadataBytes = - Arrays.copyOfRange(output.toByteArray(), 0, output.size() - Long.BYTES); - ChunksMetadataProto.ChunksMetadata actualMetadata = - ChunksMetadataProto.ChunksMetadata.parseFrom(metadataBytes); - assertThat(actualMetadata.checksumType).isEqualTo(ChunksMetadataProto.SHA_256); - assertThat(actualMetadata.cipherType).isEqualTo(ChunksMetadataProto.AES_256_GCM); - } - - @Test - public void finish_writesMetadataPosition() throws Exception { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - BackupFileBuilder builder = BackupFileBuilder.createForNonIncremental(output); - - builder.writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2), - getNewChunkMap(TEST_HASH_1, TEST_HASH_2)); - builder.writeChunks(ImmutableList.of(TEST_HASH_3), getNewChunkMap(TEST_HASH_3)); - builder.finish(getTestMetadata()); - - long expectedPosition = - (long) mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_1) - + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_2) - + mEncryptedChunkEncoder.getEncodedLengthOfChunk(TEST_CHUNK_3); - long actualPosition = - Longs.fromByteArray( - Arrays.copyOfRange( - output.toByteArray(), output.size() - Long.BYTES, output.size())); - assertThat(actualPosition).isEqualTo(expectedPosition); - } - - @Test - public void finish_flushesOutputStream() throws Exception { - OutputStream diffOutputStream = mock(OutputStream.class); - BackupFileBuilder backupFileBuilder = - BackupFileBuilder.createForIncremental( - diffOutputStream, new ChunksMetadataProto.ChunkListing()); - - backupFileBuilder.writeChunks(ImmutableList.of(TEST_HASH_1), getNewChunkMap(TEST_HASH_1)); - diffOutputStream.flush(); - - verify(diffOutputStream).flush(); - } - - private void setUpOldBackupWithChunks(List<EncryptedChunk> chunks) throws Exception { - mOldFile = mTemporaryFolder.newFile(); - ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing(); - chunkListing.fingerprintMixerSalt = - Arrays.copyOf(TEST_FINGERPRINT_MIXER_SALT, TEST_FINGERPRINT_MIXER_SALT.length); - chunkListing.cipherType = AES_256_GCM; - chunkListing.chunkOrderingType = CHUNK_ORDERING_TYPE_UNSPECIFIED; - - List<ChunksMetadataProto.Chunk> knownChunks = new ArrayList<>(); - try (FileOutputStream outputStream = new FileOutputStream(mOldFile)) { - for (EncryptedChunk chunk : chunks) { - // Chunks are encoded in the format [nonce]+[data]. - outputStream.write(chunk.nonce()); - outputStream.write(chunk.encryptedBytes()); - - knownChunks.add(createChunkFor(chunk)); - } - - outputStream.flush(); - } - - chunkListing.chunks = knownChunks.toArray(new ChunksMetadataProto.Chunk[0]); - mOldChunkListing = chunkListing; - } - - private byte[] parseDiffScript(byte[] diffScript) throws Exception { - File newFile = mTemporaryFolder.newFile(); - new DiffScriptProcessor(mOldFile, newFile).process(new ByteArrayInputStream(diffScript)); - return Files.toByteArray(newFile); - } - - private void assertListingsEqual( - ChunksMetadataProto.ChunkListing result, ChunksMetadataProto.ChunkListing expected) { - assertThat(result.chunks.length).isEqualTo(expected.chunks.length); - for (int i = 0; i < result.chunks.length; i++) { - assertWithMessage("Chunk " + i) - .that(result.chunks[i].length) - .isEqualTo(expected.chunks[i].length); - assertWithMessage("Chunk " + i) - .that(result.chunks[i].hash) - .isEqualTo(expected.chunks[i].hash); - } - } - - private static ImmutableMap<ChunkHash, EncryptedChunk> getNewChunkMap(ChunkHash... hashes) { - ImmutableMap.Builder<ChunkHash, EncryptedChunk> builder = ImmutableMap.builder(); - for (ChunkHash hash : hashes) { - if (TEST_HASH_1.equals(hash)) { - builder.put(TEST_HASH_1, TEST_CHUNK_1); - } else if (TEST_HASH_2.equals(hash)) { - builder.put(TEST_HASH_2, TEST_CHUNK_2); - } else if (TEST_HASH_3.equals(hash)) { - builder.put(TEST_HASH_3, TEST_CHUNK_3); - } else { - fail("Hash was not recognised: " + hash); - } - } - return builder.build(); - } - - private static ChunksMetadataProto.ChunksMetadata getTestMetadata() { - ChunksMetadataProto.ChunksMetadata metadata = new ChunksMetadataProto.ChunksMetadata(); - metadata.checksumType = ChunksMetadataProto.SHA_256; - metadata.cipherType = AES_256_GCM; - return metadata; - } - - private static byte[] stripMetadataAndPositionFromOutput(byte[] output) { - long metadataStart = - Longs.fromByteArray( - Arrays.copyOfRange(output, output.length - Long.BYTES, output.length)); - return Arrays.copyOfRange(output, 0, (int) metadataStart); - } - - private ChunksMetadataProto.ChunkListing expectedChunkListing() { - ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing(); - chunkListing.fingerprintMixerSalt = - Arrays.copyOf(TEST_FINGERPRINT_MIXER_SALT, TEST_FINGERPRINT_MIXER_SALT.length); - chunkListing.cipherType = AES_256_GCM; - chunkListing.chunkOrderingType = CHUNK_ORDERING_TYPE_UNSPECIFIED; - chunkListing.chunks = new ChunksMetadataProto.Chunk[3]; - chunkListing.chunks[0] = createChunkFor(TEST_CHUNK_1); - chunkListing.chunks[1] = createChunkFor(TEST_CHUNK_2); - chunkListing.chunks[2] = createChunkFor(TEST_CHUNK_3); - return chunkListing; - } - - private ChunksMetadataProto.Chunk createChunkFor(EncryptedChunk encryptedChunk) { - byte[] chunkHash = encryptedChunk.key().getHash(); - byte[] hashCopy = Arrays.copyOf(chunkHash, chunkHash.length); - return newChunk(hashCopy, mEncryptedChunkEncoder.getEncodedLengthOfChunk(encryptedChunk)); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java deleted file mode 100644 index 8df08262c9fa..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ByteRangeTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import static org.junit.Assert.assertEquals; -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -/** Tests for {@link ByteRange}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class ByteRangeTest { - @Test - public void getLength_includesEnd() throws Exception { - ByteRange byteRange = new ByteRange(5, 10); - - int length = byteRange.getLength(); - - assertEquals(6, length); - } - - @Test - public void constructor_rejectsNegativeStart() { - assertThrows(IllegalArgumentException.class, () -> new ByteRange(-1, 10)); - } - - @Test - public void constructor_rejectsEndBeforeStart() { - assertThrows(IllegalArgumentException.class, () -> new ByteRange(10, 9)); - } - - @Test - public void extend_withZeroLength_throwsException() { - ByteRange byteRange = new ByteRange(5, 10); - - assertThrows(IllegalArgumentException.class, () -> byteRange.extend(0)); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java deleted file mode 100644 index 19e3b28f85e7..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkEncryptorTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.mock; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.robolectric.RobolectricTestRunner; - -import java.security.SecureRandom; - -import javax.crypto.Cipher; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.GCMParameterSpec; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class ChunkEncryptorTest { - private static final String MAC_ALGORITHM = "HmacSHA256"; - private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; - private static final int GCM_NONCE_LENGTH_BYTES = 12; - private static final int GCM_TAG_LENGTH_BYTES = 16; - private static final String CHUNK_PLAINTEXT = - "A little Learning is a dang'rous Thing;\n" - + "Drink deep, or taste not the Pierian Spring:\n" - + "There shallow Draughts intoxicate the Brain,\n" - + "And drinking largely sobers us again."; - private static final byte[] PLAINTEXT_BYTES = CHUNK_PLAINTEXT.getBytes(UTF_8); - private static final byte[] NONCE_1 = "0123456789abc".getBytes(UTF_8); - private static final byte[] NONCE_2 = "123456789abcd".getBytes(UTF_8); - - private static final byte[][] NONCES = new byte[][] {NONCE_1, NONCE_2}; - - @Mock private SecureRandom mSecureRandomMock; - private SecretKey mSecretKey; - private ChunkHash mPlaintextHash; - private ChunkEncryptor mChunkEncryptor; - - @Before - public void setUp() throws Exception { - mSecretKey = generateAesKey(); - ChunkHasher chunkHasher = new ChunkHasher(mSecretKey); - mPlaintextHash = chunkHasher.computeHash(PLAINTEXT_BYTES); - mSecureRandomMock = mock(SecureRandom.class); - mChunkEncryptor = new ChunkEncryptor(mSecretKey, mSecureRandomMock); - - // Return NONCE_1, then NONCE_2 for invocations of mSecureRandomMock.nextBytes(). - doAnswer( - new Answer<Void>() { - private int mInvocation = 0; - - @Override - public Void answer(InvocationOnMock invocation) { - byte[] nonceDestination = invocation.getArgument(0); - System.arraycopy( - NONCES[this.mInvocation], - 0, - nonceDestination, - 0, - GCM_NONCE_LENGTH_BYTES); - this.mInvocation++; - return null; - } - }) - .when(mSecureRandomMock) - .nextBytes(any(byte[].class)); - } - - @Test - public void encrypt_withHash_resultContainsHashAsKey() throws Exception { - EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); - - assertThat(chunk.key()).isEqualTo(mPlaintextHash); - } - - @Test - public void encrypt_generatesHmacOfPlaintext() throws Exception { - EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); - - byte[] generatedHash = chunk.key().getHash(); - Mac mac = Mac.getInstance(MAC_ALGORITHM); - mac.init(mSecretKey); - byte[] plaintextHmac = mac.doFinal(PLAINTEXT_BYTES); - assertThat(generatedHash).isEqualTo(plaintextHmac); - } - - @Test - public void encrypt_whenInvokedAgain_generatesNewNonce() throws Exception { - EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); - - EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); - - assertThat(chunk1.nonce()).isNotEqualTo(chunk2.nonce()); - } - - @Test - public void encrypt_whenInvokedAgain_generatesNewCiphertext() throws Exception { - EncryptedChunk chunk1 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); - - EncryptedChunk chunk2 = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); - - assertThat(chunk1.encryptedBytes()).isNotEqualTo(chunk2.encryptedBytes()); - } - - @Test - public void encrypt_generates12ByteNonce() throws Exception { - EncryptedChunk encryptedChunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); - - byte[] nonce = encryptedChunk.nonce(); - assertThat(nonce).hasLength(GCM_NONCE_LENGTH_BYTES); - } - - @Test - public void encrypt_decryptedResultCorrespondsToPlaintext() throws Exception { - EncryptedChunk chunk = mChunkEncryptor.encrypt(mPlaintextHash, PLAINTEXT_BYTES); - - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init( - Cipher.DECRYPT_MODE, - mSecretKey, - new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * 8, chunk.nonce())); - byte[] decrypted = cipher.doFinal(chunk.encryptedBytes()); - assertThat(decrypted).isEqualTo(PLAINTEXT_BYTES); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java deleted file mode 100644 index 72a927db743d..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ChunkHasherTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class ChunkHasherTest { - private static final String KEY_ALGORITHM = "AES"; - private static final String MAC_ALGORITHM = "HmacSHA256"; - - private static final byte[] TEST_KEY = {100, 120}; - private static final byte[] TEST_DATA = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}; - - private SecretKey mSecretKey; - private ChunkHasher mChunkHasher; - - @Before - public void setUp() throws Exception { - mSecretKey = new SecretKeySpec(TEST_KEY, KEY_ALGORITHM); - mChunkHasher = new ChunkHasher(mSecretKey); - } - - @Test - public void computeHash_returnsHmacForData() throws Exception { - ChunkHash chunkHash = mChunkHasher.computeHash(TEST_DATA); - - byte[] hash = chunkHash.getHash(); - Mac mac = Mac.getInstance(MAC_ALGORITHM); - mac.init(mSecretKey); - byte[] expectedHash = mac.doFinal(TEST_DATA); - assertThat(hash).isEqualTo(expectedHash); - } - - @Test - public void computeHash_generates256BitHmac() throws Exception { - int expectedLength = 256 / Byte.SIZE; - - byte[] hash = mChunkHasher.computeHash(TEST_DATA).getHash(); - - assertThat(hash).hasLength(expectedLength); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java deleted file mode 100644 index 823a63c22da4..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DecryptedChunkFileOutputTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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.chunking; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.tasks.DecryptedChunkOutput; - -import com.google.common.io.Files; -import com.google.common.primitives.Bytes; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.security.MessageDigest; -import java.util.Arrays; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class DecryptedChunkFileOutputTest { - private static final byte[] TEST_CHUNK_1 = {1, 2, 3}; - private static final byte[] TEST_CHUNK_2 = {4, 5, 6, 7, 8, 9, 10}; - private static final int TEST_BUFFER_LENGTH = - Math.max(TEST_CHUNK_1.length, TEST_CHUNK_2.length); - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - private File mOutputFile; - private DecryptedChunkFileOutput mDecryptedChunkFileOutput; - - @Before - public void setUp() throws Exception { - mOutputFile = temporaryFolder.newFile(); - mDecryptedChunkFileOutput = new DecryptedChunkFileOutput(mOutputFile); - } - - @Test - public void open_returnsInstance() throws Exception { - DecryptedChunkOutput result = mDecryptedChunkFileOutput.open(); - assertThat(result).isEqualTo(mDecryptedChunkFileOutput); - } - - @Test - public void open_nonExistentOutputFolder_throwsException() throws Exception { - mDecryptedChunkFileOutput = - new DecryptedChunkFileOutput( - new File(temporaryFolder.newFolder(), "mOutput/directory")); - assertThrows(FileNotFoundException.class, () -> mDecryptedChunkFileOutput.open()); - } - - @Test - public void open_whenRunTwice_throwsException() throws Exception { - mDecryptedChunkFileOutput.open(); - assertThrows(IllegalStateException.class, () -> mDecryptedChunkFileOutput.open()); - } - - @Test - public void processChunk_beforeOpen_throwsException() throws Exception { - assertThrows(IllegalStateException.class, - () -> mDecryptedChunkFileOutput.processChunk(new byte[0], 0)); - } - - @Test - public void processChunk_writesChunksToFile() throws Exception { - processTestChunks(); - - assertThat(Files.toByteArray(mOutputFile)) - .isEqualTo(Bytes.concat(TEST_CHUNK_1, TEST_CHUNK_2)); - } - - @Test - public void getDigest_beforeClose_throws() throws Exception { - mDecryptedChunkFileOutput.open(); - assertThrows(IllegalStateException.class, () -> mDecryptedChunkFileOutput.getDigest()); - } - - @Test - public void getDigest_returnsCorrectDigest() throws Exception { - processTestChunks(); - - byte[] actualDigest = mDecryptedChunkFileOutput.getDigest(); - - MessageDigest expectedDigest = - MessageDigest.getInstance(DecryptedChunkFileOutput.DIGEST_ALGORITHM); - expectedDigest.update(TEST_CHUNK_1); - expectedDigest.update(TEST_CHUNK_2); - assertThat(actualDigest).isEqualTo(expectedDigest.digest()); - } - - @Test - public void getDigest_whenRunTwice_returnsIdenticalDigestBothTimes() throws Exception { - processTestChunks(); - - byte[] digest1 = mDecryptedChunkFileOutput.getDigest(); - byte[] digest2 = mDecryptedChunkFileOutput.getDigest(); - - assertThat(digest1).isEqualTo(digest2); - } - - private void processTestChunks() throws IOException { - mDecryptedChunkFileOutput.open(); - mDecryptedChunkFileOutput.processChunk(Arrays.copyOf(TEST_CHUNK_1, TEST_BUFFER_LENGTH), - TEST_CHUNK_1.length); - mDecryptedChunkFileOutput.processChunk(Arrays.copyOf(TEST_CHUNK_2, TEST_BUFFER_LENGTH), - TEST_CHUNK_2.length); - mDecryptedChunkFileOutput.close(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java deleted file mode 100644 index 2af6f2bee8ff..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/DiffScriptBackupWriterTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.atLeastOnce; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.platform.test.annotations.Presubmit; - -import com.google.common.primitives.Bytes; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.robolectric.RobolectricTestRunner; - -import java.io.IOException; - -/** Tests for {@link DiffScriptBackupWriter}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class DiffScriptBackupWriterTest { - private static final byte[] TEST_BYTES = {1, 2, 3, 4, 5, 6, 7, 8, 9}; - - @Captor private ArgumentCaptor<Byte> mBytesCaptor; - @Mock private SingleStreamDiffScriptWriter mDiffScriptWriter; - private BackupWriter mBackupWriter; - - @Before - public void setUp() { - mDiffScriptWriter = mock(SingleStreamDiffScriptWriter.class); - mBackupWriter = new DiffScriptBackupWriter(mDiffScriptWriter); - mBytesCaptor = ArgumentCaptor.forClass(Byte.class); - } - - @Test - public void writeBytes_writesBytesToWriter() throws Exception { - mBackupWriter.writeBytes(TEST_BYTES); - - verify(mDiffScriptWriter, atLeastOnce()).writeByte(mBytesCaptor.capture()); - assertThat(mBytesCaptor.getAllValues()) - .containsExactlyElementsIn(Bytes.asList(TEST_BYTES)) - .inOrder(); - } - - @Test - public void writeChunk_writesChunkToWriter() throws Exception { - mBackupWriter.writeChunk(0, 10); - - verify(mDiffScriptWriter).writeChunk(0, 10); - } - - @Test - public void getBytesWritten_returnsTotalSum() throws Exception { - mBackupWriter.writeBytes(TEST_BYTES); - mBackupWriter.writeBytes(TEST_BYTES); - mBackupWriter.writeChunk(/*start=*/ 0, /*length=*/ 10); - - long bytesWritten = mBackupWriter.getBytesWritten(); - - assertThat(bytesWritten).isEqualTo(2 * TEST_BYTES.length + 10); - } - - @Test - public void flush_flushesWriter() throws IOException { - mBackupWriter.flush(); - - verify(mDiffScriptWriter).flush(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java deleted file mode 100644 index 325b601e2ccb..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/EncryptedChunkTest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; - -import com.google.common.primitives.Bytes; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.util.Arrays; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class EncryptedChunkTest { - private static final byte[] CHUNK_HASH_1_BYTES = - Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES); - private static final byte[] NONCE_1 = - Arrays.copyOf(new byte[] {2}, EncryptedChunk.NONCE_LENGTH_BYTES); - private static final byte[] ENCRYPTED_BYTES_1 = - Arrays.copyOf(new byte[] {3}, EncryptedChunk.KEY_LENGTH_BYTES); - - private static final byte[] CHUNK_HASH_2_BYTES = - Arrays.copyOf(new byte[] {4}, ChunkHash.HASH_LENGTH_BYTES); - private static final byte[] NONCE_2 = - Arrays.copyOf(new byte[] {5}, EncryptedChunk.NONCE_LENGTH_BYTES); - private static final byte[] ENCRYPTED_BYTES_2 = - Arrays.copyOf(new byte[] {6}, EncryptedChunk.KEY_LENGTH_BYTES); - - @Test - public void testCreate_withIncorrectLength_throwsException() { - ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES); - byte[] shortNonce = Arrays.copyOf(new byte[] {2}, EncryptedChunk.NONCE_LENGTH_BYTES - 1); - - assertThrows( - IllegalArgumentException.class, - () -> EncryptedChunk.create(chunkHash, shortNonce, ENCRYPTED_BYTES_1)); - } - - @Test - public void testEncryptedBytes_forNewlyCreatedObject_returnsCorrectValue() { - ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES); - EncryptedChunk encryptedChunk = - EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1); - - byte[] returnedBytes = encryptedChunk.encryptedBytes(); - - assertThat(returnedBytes) - .asList() - .containsExactlyElementsIn(Bytes.asList(ENCRYPTED_BYTES_1)) - .inOrder(); - } - - @Test - public void testKey_forNewlyCreatedObject_returnsCorrectValue() { - ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES); - EncryptedChunk encryptedChunk = - EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1); - - ChunkHash returnedKey = encryptedChunk.key(); - - assertThat(returnedKey).isEqualTo(chunkHash); - } - - @Test - public void testNonce_forNewlycreatedObject_returnCorrectValue() { - ChunkHash chunkHash = new ChunkHash(CHUNK_HASH_1_BYTES); - EncryptedChunk encryptedChunk = - EncryptedChunk.create(chunkHash, NONCE_1, ENCRYPTED_BYTES_1); - - byte[] returnedNonce = encryptedChunk.nonce(); - - assertThat(returnedNonce).asList().containsExactlyElementsIn(Bytes.asList(NONCE_1)); - } - - @Test - public void testEquals() { - ChunkHash chunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES); - ChunkHash equalChunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES); - ChunkHash chunkHash2 = new ChunkHash(CHUNK_HASH_2_BYTES); - EncryptedChunk encryptedChunk1 = - EncryptedChunk.create(chunkHash1, NONCE_1, ENCRYPTED_BYTES_1); - EncryptedChunk equalEncryptedChunk1 = - EncryptedChunk.create(equalChunkHash1, NONCE_1, ENCRYPTED_BYTES_1); - EncryptedChunk encryptedChunk2 = - EncryptedChunk.create(chunkHash2, NONCE_2, ENCRYPTED_BYTES_2); - - assertThat(encryptedChunk1).isEqualTo(equalEncryptedChunk1); - assertThat(encryptedChunk1).isNotEqualTo(encryptedChunk2); - } - - @Test - public void testHashCode() { - ChunkHash chunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES); - ChunkHash equalChunkHash1 = new ChunkHash(CHUNK_HASH_1_BYTES); - ChunkHash chunkHash2 = new ChunkHash(CHUNK_HASH_2_BYTES); - EncryptedChunk encryptedChunk1 = - EncryptedChunk.create(chunkHash1, NONCE_1, ENCRYPTED_BYTES_1); - EncryptedChunk equalEncryptedChunk1 = - EncryptedChunk.create(equalChunkHash1, NONCE_1, ENCRYPTED_BYTES_1); - EncryptedChunk encryptedChunk2 = - EncryptedChunk.create(chunkHash2, NONCE_2, ENCRYPTED_BYTES_2); - - int hash1 = encryptedChunk1.hashCode(); - int equalHash1 = equalEncryptedChunk1.hashCode(); - int hash2 = encryptedChunk2.hashCode(); - - assertThat(hash1).isEqualTo(equalHash1); - assertThat(hash1).isNotEqualTo(hash2); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java deleted file mode 100644 index 7e1fdedcac80..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/InlineLengthsEncryptedChunkEncoderTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.robolectric.RobolectricTestRunner; - -import java.util.Arrays; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class InlineLengthsEncryptedChunkEncoderTest { - - private static final byte[] TEST_NONCE = - Arrays.copyOf(new byte[] {1}, EncryptedChunk.NONCE_LENGTH_BYTES); - private static final byte[] TEST_KEY_DATA = - Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES); - private static final byte[] TEST_DATA = {5, 4, 5, 7, 10, 12, 1, 2, 9}; - - @Mock private BackupWriter mMockBackupWriter; - private ChunkHash mTestKey; - private EncryptedChunk mTestChunk; - private EncryptedChunkEncoder mEncoder; - - @Before - public void setUp() throws Exception { - mMockBackupWriter = mock(BackupWriter.class); - mTestKey = new ChunkHash(TEST_KEY_DATA); - mTestChunk = EncryptedChunk.create(mTestKey, TEST_NONCE, TEST_DATA); - mEncoder = new InlineLengthsEncryptedChunkEncoder(); - } - - @Test - public void writeChunkToWriter_writesLengthThenNonceThenData() throws Exception { - mEncoder.writeChunkToWriter(mMockBackupWriter, mTestChunk); - - InOrder inOrder = inOrder(mMockBackupWriter); - inOrder.verify(mMockBackupWriter) - .writeBytes( - InlineLengthsEncryptedChunkEncoder.toByteArray( - TEST_NONCE.length + TEST_DATA.length)); - inOrder.verify(mMockBackupWriter).writeBytes(TEST_NONCE); - inOrder.verify(mMockBackupWriter).writeBytes(TEST_DATA); - } - - @Test - public void getEncodedLengthOfChunk_returnsSumOfNonceAndDataLengths() { - int encodedLength = mEncoder.getEncodedLengthOfChunk(mTestChunk); - - assertThat(encodedLength).isEqualTo(Integer.BYTES + TEST_NONCE.length + TEST_DATA.length); - } - - @Test - public void getChunkOrderingType_returnsExplicitStartsType() { - assertThat(mEncoder.getChunkOrderingType()).isEqualTo(ChunksMetadataProto.INLINE_LENGTHS); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java deleted file mode 100644 index 6f58ee148b83..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/LengthlessEncryptedChunkEncoderTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.mock; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.robolectric.RobolectricTestRunner; - -import java.util.Arrays; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class LengthlessEncryptedChunkEncoderTest { - private static final byte[] TEST_NONCE = - Arrays.copyOf(new byte[] {1}, EncryptedChunk.NONCE_LENGTH_BYTES); - private static final byte[] TEST_KEY_DATA = - Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES); - private static final byte[] TEST_DATA = {5, 4, 5, 7, 10, 12, 1, 2, 9}; - - @Mock private BackupWriter mMockBackupWriter; - private ChunkHash mTestKey; - private EncryptedChunk mTestChunk; - private EncryptedChunkEncoder mEncoder; - - @Before - public void setUp() throws Exception { - mMockBackupWriter = mock(BackupWriter.class); - mTestKey = new ChunkHash(TEST_KEY_DATA); - mTestChunk = EncryptedChunk.create(mTestKey, TEST_NONCE, TEST_DATA); - mEncoder = new LengthlessEncryptedChunkEncoder(); - } - - @Test - public void writeChunkToWriter_writesNonceThenData() throws Exception { - mEncoder.writeChunkToWriter(mMockBackupWriter, mTestChunk); - - InOrder inOrder = inOrder(mMockBackupWriter); - inOrder.verify(mMockBackupWriter).writeBytes(TEST_NONCE); - inOrder.verify(mMockBackupWriter).writeBytes(TEST_DATA); - } - - @Test - public void getEncodedLengthOfChunk_returnsSumOfNonceAndDataLengths() { - int encodedLength = mEncoder.getEncodedLengthOfChunk(mTestChunk); - - assertThat(encodedLength).isEqualTo(TEST_NONCE.length + TEST_DATA.length); - } - - @Test - public void getChunkOrderingType_returnsExplicitStartsType() { - assertThat(mEncoder.getChunkOrderingType()).isEqualTo(ChunksMetadataProto.EXPLICIT_STARTS); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ProtoStoreTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ProtoStoreTest.java deleted file mode 100644 index d73c8e47f609..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/ProtoStoreTest.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * 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.chunking; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.testng.Assert.assertThrows; - -import android.content.Context; -import android.platform.test.annotations.Presubmit; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.protos.nano.KeyValueListingProto; - -import com.google.common.collect.ImmutableMap; - -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map.Entry; -import java.util.Optional; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class ProtoStoreTest { - private static final String TEST_KEY_1 = "test_key_1"; - private static final ChunkHash TEST_HASH_1 = - new ChunkHash(Arrays.copyOf(new byte[] {1}, EncryptedChunk.KEY_LENGTH_BYTES)); - private static final ChunkHash TEST_HASH_2 = - new ChunkHash(Arrays.copyOf(new byte[] {2}, EncryptedChunk.KEY_LENGTH_BYTES)); - private static final int TEST_LENGTH_1 = 10; - private static final int TEST_LENGTH_2 = 18; - - private static final String TEST_PACKAGE_1 = "com.example.test1"; - private static final String TEST_PACKAGE_2 = "com.example.test2"; - - @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - - private File mStoreFolder; - private ProtoStore<ChunksMetadataProto.ChunkListing> mProtoStore; - - @Before - public void setUp() throws Exception { - mStoreFolder = mTemporaryFolder.newFolder(); - mProtoStore = new ProtoStore<>(ChunksMetadataProto.ChunkListing.class, mStoreFolder); - } - - @Test - public void differentStoreTypes_operateSimultaneouslyWithoutInterfering() throws Exception { - ChunksMetadataProto.ChunkListing chunkListing = - createChunkListing(ImmutableMap.of(TEST_HASH_1, TEST_LENGTH_1)); - KeyValueListingProto.KeyValueListing keyValueListing = - new KeyValueListingProto.KeyValueListing(); - keyValueListing.entries = new KeyValueListingProto.KeyValueEntry[1]; - keyValueListing.entries[0] = new KeyValueListingProto.KeyValueEntry(); - keyValueListing.entries[0].key = TEST_KEY_1; - keyValueListing.entries[0].hash = TEST_HASH_1.getHash(); - - Context application = ApplicationProvider.getApplicationContext(); - ProtoStore<ChunksMetadataProto.ChunkListing> chunkListingStore = - ProtoStore.createChunkListingStore(application); - ProtoStore<KeyValueListingProto.KeyValueListing> keyValueListingStore = - ProtoStore.createKeyValueListingStore(application); - - chunkListingStore.saveProto(TEST_PACKAGE_1, chunkListing); - keyValueListingStore.saveProto(TEST_PACKAGE_1, keyValueListing); - - ChunksMetadataProto.ChunkListing actualChunkListing = - chunkListingStore.loadProto(TEST_PACKAGE_1).get(); - KeyValueListingProto.KeyValueListing actualKeyValueListing = - keyValueListingStore.loadProto(TEST_PACKAGE_1).get(); - assertListingsEqual(actualChunkListing, chunkListing); - assertThat(actualKeyValueListing.entries.length).isEqualTo(1); - assertThat(actualKeyValueListing.entries[0].key).isEqualTo(TEST_KEY_1); - assertThat(actualKeyValueListing.entries[0].hash).isEqualTo(TEST_HASH_1.getHash()); - } - - @Test - public void construct_storeLocationIsFile_throws() throws Exception { - assertThrows( - IOException.class, - () -> - new ProtoStore<>( - ChunksMetadataProto.ChunkListing.class, - mTemporaryFolder.newFile())); - } - - @Test - public void loadChunkListing_noListingExists_returnsEmptyListing() throws Exception { - Optional<ChunksMetadataProto.ChunkListing> chunkListing = - mProtoStore.loadProto(TEST_PACKAGE_1); - assertThat(chunkListing.isPresent()).isFalse(); - } - - @Test - public void loadChunkListing_listingExists_returnsExistingListing() throws Exception { - ChunksMetadataProto.ChunkListing expected = - createChunkListing( - ImmutableMap.of(TEST_HASH_1, TEST_LENGTH_1, TEST_HASH_2, TEST_LENGTH_2)); - mProtoStore.saveProto(TEST_PACKAGE_1, expected); - - ChunksMetadataProto.ChunkListing result = mProtoStore.loadProto(TEST_PACKAGE_1).get(); - - assertListingsEqual(result, expected); - } - - @Test - public void loadProto_emptyPackageName_throwsException() throws Exception { - assertThrows(IllegalArgumentException.class, () -> mProtoStore.loadProto("")); - } - - @Test - public void loadProto_nullPackageName_throwsException() throws Exception { - assertThrows(IllegalArgumentException.class, () -> mProtoStore.loadProto(null)); - } - - @Test - public void loadProto_packageNameContainsSlash_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, () -> mProtoStore.loadProto(TEST_PACKAGE_1 + "/")); - } - - @Test - public void saveProto_persistsToNewInstance() throws Exception { - ChunksMetadataProto.ChunkListing expected = - createChunkListing( - ImmutableMap.of(TEST_HASH_1, TEST_LENGTH_1, TEST_HASH_2, TEST_LENGTH_2)); - mProtoStore.saveProto(TEST_PACKAGE_1, expected); - mProtoStore = new ProtoStore<>(ChunksMetadataProto.ChunkListing.class, mStoreFolder); - - ChunksMetadataProto.ChunkListing result = mProtoStore.loadProto(TEST_PACKAGE_1).get(); - - assertListingsEqual(result, expected); - } - - @Test - public void saveProto_emptyPackageName_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> mProtoStore.saveProto("", new ChunksMetadataProto.ChunkListing())); - } - - @Test - public void saveProto_nullPackageName_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> mProtoStore.saveProto(null, new ChunksMetadataProto.ChunkListing())); - } - - @Test - public void saveProto_packageNameContainsSlash_throwsException() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> - mProtoStore.saveProto( - TEST_PACKAGE_1 + "/", new ChunksMetadataProto.ChunkListing())); - } - - @Test - public void saveProto_nullListing_throwsException() throws Exception { - assertThrows(NullPointerException.class, () -> mProtoStore.saveProto(TEST_PACKAGE_1, null)); - } - - @Test - public void deleteProto_noListingExists_doesNothing() throws Exception { - ChunksMetadataProto.ChunkListing listing = - createChunkListing(ImmutableMap.of(TEST_HASH_1, TEST_LENGTH_1)); - mProtoStore.saveProto(TEST_PACKAGE_1, listing); - - mProtoStore.deleteProto(TEST_PACKAGE_2); - - assertThat(mProtoStore.loadProto(TEST_PACKAGE_1).get().chunks.length).isEqualTo(1); - } - - @Test - public void deleteProto_listingExists_deletesListing() throws Exception { - ChunksMetadataProto.ChunkListing listing = - createChunkListing(ImmutableMap.of(TEST_HASH_1, TEST_LENGTH_1)); - mProtoStore.saveProto(TEST_PACKAGE_1, listing); - - mProtoStore.deleteProto(TEST_PACKAGE_1); - - assertThat(mProtoStore.loadProto(TEST_PACKAGE_1).isPresent()).isFalse(); - } - - @Test - public void deleteAllProtos_deletesAllProtos() throws Exception { - ChunksMetadataProto.ChunkListing listing1 = - createChunkListing(ImmutableMap.of(TEST_HASH_1, TEST_LENGTH_1)); - ChunksMetadataProto.ChunkListing listing2 = - createChunkListing(ImmutableMap.of(TEST_HASH_2, TEST_LENGTH_2)); - mProtoStore.saveProto(TEST_PACKAGE_1, listing1); - mProtoStore.saveProto(TEST_PACKAGE_2, listing2); - - mProtoStore.deleteAllProtos(); - - assertThat(mProtoStore.loadProto(TEST_PACKAGE_1).isPresent()).isFalse(); - assertThat(mProtoStore.loadProto(TEST_PACKAGE_2).isPresent()).isFalse(); - } - - @Test - public void deleteAllProtos_folderDeleted_doesNotCrash() throws Exception { - mStoreFolder.delete(); - - mProtoStore.deleteAllProtos(); - } - - private static ChunksMetadataProto.ChunkListing createChunkListing( - ImmutableMap<ChunkHash, Integer> chunks) { - ChunksMetadataProto.ChunkListing listing = new ChunksMetadataProto.ChunkListing(); - listing.cipherType = ChunksMetadataProto.AES_256_GCM; - listing.chunkOrderingType = ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED; - - List<ChunksMetadataProto.Chunk> chunkProtos = new ArrayList<>(); - for (Entry<ChunkHash, Integer> entry : chunks.entrySet()) { - ChunksMetadataProto.Chunk chunk = new ChunksMetadataProto.Chunk(); - chunk.hash = entry.getKey().getHash(); - chunk.length = entry.getValue(); - chunkProtos.add(chunk); - } - listing.chunks = chunkProtos.toArray(new ChunksMetadataProto.Chunk[0]); - return listing; - } - - private void assertListingsEqual( - ChunksMetadataProto.ChunkListing result, ChunksMetadataProto.ChunkListing expected) { - assertThat(result.chunks.length).isEqualTo(expected.chunks.length); - for (int i = 0; i < result.chunks.length; i++) { - assertWithMessage("Chunk " + i) - .that(result.chunks[i].length) - .isEqualTo(expected.chunks[i].length); - assertWithMessage("Chunk " + i) - .that(result.chunks[i].hash) - .isEqualTo(expected.chunks[i].hash); - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java deleted file mode 100644 index 966d3e2d583d..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/RawBackupWriterTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import com.google.common.primitives.Bytes; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.io.ByteArrayOutputStream; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class RawBackupWriterTest { - private static final byte[] TEST_BYTES = {1, 2, 3, 4, 5, 6}; - - private BackupWriter mWriter; - private ByteArrayOutputStream mOutput; - - @Before - public void setUp() { - mOutput = new ByteArrayOutputStream(); - mWriter = new RawBackupWriter(mOutput); - } - - @Test - public void writeBytes_writesToOutputStream() throws Exception { - mWriter.writeBytes(TEST_BYTES); - - assertThat(mOutput.toByteArray()) - .asList() - .containsExactlyElementsIn(Bytes.asList(TEST_BYTES)) - .inOrder(); - } - - @Test - public void writeChunk_throwsUnsupportedOperationException() throws Exception { - assertThrows(UnsupportedOperationException.class, () -> mWriter.writeChunk(0, 0)); - } - - @Test - public void getBytesWritten_returnsTotalSum() throws Exception { - mWriter.writeBytes(TEST_BYTES); - mWriter.writeBytes(TEST_BYTES); - - long bytesWritten = mWriter.getBytesWritten(); - - assertThat(bytesWritten).isEqualTo(2 * TEST_BYTES.length); - } - - @Test - public void flush_flushesOutputStream() throws Exception { - mOutput = mock(ByteArrayOutputStream.class); - mWriter = new RawBackupWriter(mOutput); - - mWriter.flush(); - - verify(mOutput).flush(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java deleted file mode 100644 index 73baf80a2c70..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/SingleStreamDiffScriptWriterTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Locale; - -/** Tests for {@link SingleStreamDiffScriptWriter}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class SingleStreamDiffScriptWriterTest { - private static final int MAX_CHUNK_SIZE_IN_BYTES = 256; - /** By default this Locale does not use Arabic numbers for %d formatting. */ - private static final Locale HINDI = new Locale("hi", "IN"); - - private Locale mDefaultLocale; - private ByteArrayOutputStream mOutputStream; - private SingleStreamDiffScriptWriter mDiffScriptWriter; - - @Before - public void setUp() { - mDefaultLocale = Locale.getDefault(); - mOutputStream = new ByteArrayOutputStream(); - mDiffScriptWriter = - new SingleStreamDiffScriptWriter(mOutputStream, MAX_CHUNK_SIZE_IN_BYTES); - } - - @After - public void tearDown() { - Locale.setDefault(mDefaultLocale); - } - - @Test - public void writeChunk_withNegativeStart_throwsException() { - assertThrows( - IllegalArgumentException.class, - () -> mDiffScriptWriter.writeChunk(-1, 50)); - } - - @Test - public void writeChunk_withZeroLength_throwsException() { - assertThrows( - IllegalArgumentException.class, - () -> mDiffScriptWriter.writeChunk(0, 0)); - } - - @Test - public void writeChunk_withExistingBytesInBuffer_writesBufferFirst() - throws IOException { - String testString = "abcd"; - writeStringAsBytesToWriter(testString, mDiffScriptWriter); - - mDiffScriptWriter.writeChunk(0, 20); - mDiffScriptWriter.flush(); - - // Expected format: length of abcd, newline, abcd, newline, chunk start - chunk end - assertThat(mOutputStream.toString("UTF-8")).isEqualTo( - String.format("%d\n%s\n%d-%d\n", testString.length(), testString, 0, 19)); - } - - @Test - public void writeChunk_overlappingPreviousChunk_combinesChunks() throws IOException { - mDiffScriptWriter.writeChunk(3, 4); - - mDiffScriptWriter.writeChunk(7, 5); - mDiffScriptWriter.flush(); - - assertThat(mOutputStream.toString("UTF-8")).isEqualTo(String.format("3-11\n")); - } - - @Test - public void writeChunk_formatsByteIndexesUsingArabicNumbers() throws Exception { - Locale.setDefault(HINDI); - - mDiffScriptWriter.writeChunk(0, 12345); - mDiffScriptWriter.flush(); - - assertThat(mOutputStream.toString("UTF-8")).isEqualTo("0-12344\n"); - } - - @Test - public void flush_flushesOutputStream() throws IOException { - ByteArrayOutputStream mockOutputStream = mock(ByteArrayOutputStream.class); - SingleStreamDiffScriptWriter diffScriptWriter = - new SingleStreamDiffScriptWriter(mockOutputStream, MAX_CHUNK_SIZE_IN_BYTES); - - diffScriptWriter.flush(); - - verify(mockOutputStream).flush(); - } - - private void writeStringAsBytesToWriter(String string, SingleStreamDiffScriptWriter writer) - throws IOException { - byte[] bytes = string.getBytes("UTF-8"); - for (int i = 0; i < bytes.length; i++) { - writer.writeByte(bytes[i]); - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java deleted file mode 100644 index 77b734785424..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/ContentDefinedChunkerTest.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Random; - -import javax.crypto.SecretKey; - -/** Tests for {@link ContentDefinedChunker}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class ContentDefinedChunkerTest { - private static final int WINDOW_SIZE_BYTES = 31; - private static final int MIN_SIZE_BYTES = 40; - private static final int MAX_SIZE_BYTES = 300; - private static final String CHUNK_BOUNDARY = "<----------BOUNDARY----------->"; - private static final byte[] CHUNK_BOUNDARY_BYTES = CHUNK_BOUNDARY.getBytes(UTF_8); - private static final String CHUNK_1 = "This is the first chunk"; - private static final String CHUNK_2 = "And this is the second chunk"; - private static final String CHUNK_3 = "And finally here is the third chunk"; - private static final String SMALL_CHUNK = "12345678"; - - private FingerprintMixer mFingerprintMixer; - private RabinFingerprint64 mRabinFingerprint64; - private ContentDefinedChunker mChunker; - - /** Set up a {@link ContentDefinedChunker} and dependencies for use in the tests. */ - @Before - public void setUp() throws Exception { - SecretKey secretKey = generateAesKey(); - byte[] salt = new byte[FingerprintMixer.SALT_LENGTH_BYTES]; - Random random = new Random(); - random.nextBytes(salt); - mFingerprintMixer = new FingerprintMixer(secretKey, salt); - - mRabinFingerprint64 = new RabinFingerprint64(); - long chunkBoundaryFingerprint = calculateFingerprint(CHUNK_BOUNDARY_BYTES); - mChunker = - new ContentDefinedChunker( - MIN_SIZE_BYTES, - MAX_SIZE_BYTES, - mRabinFingerprint64, - mFingerprintMixer, - (fingerprint) -> fingerprint == chunkBoundaryFingerprint); - } - - /** - * Creating a {@link ContentDefinedChunker} with a minimum chunk size that is smaller than the - * window size should throw an {@link IllegalArgumentException}. - */ - @Test - public void create_withMinChunkSizeSmallerThanWindowSize_throwsIllegalArgumentException() { - assertThrows( - IllegalArgumentException.class, - () -> - new ContentDefinedChunker( - WINDOW_SIZE_BYTES - 1, - MAX_SIZE_BYTES, - mRabinFingerprint64, - mFingerprintMixer, - null)); - } - - /** - * Creating a {@link ContentDefinedChunker} with a maximum chunk size that is smaller than the - * minimum chunk size should throw an {@link IllegalArgumentException}. - */ - @Test - public void create_withMaxChunkSizeSmallerThanMinChunkSize_throwsIllegalArgumentException() { - assertThrows( - IllegalArgumentException.class, - () -> - new ContentDefinedChunker( - MIN_SIZE_BYTES, - MIN_SIZE_BYTES - 1, - mRabinFingerprint64, - mFingerprintMixer, - null)); - } - - /** - * {@link ContentDefinedChunker#chunkify(InputStream, Chunker.ChunkConsumer)} should split the - * input stream across chunk boundaries by default. - */ - @Test - public void chunkify_withLargeChunks_splitsIntoChunksAcrossBoundaries() throws Exception { - byte[] input = - (CHUNK_1 + CHUNK_BOUNDARY + CHUNK_2 + CHUNK_BOUNDARY + CHUNK_3).getBytes(UTF_8); - ByteArrayInputStream inputStream = new ByteArrayInputStream(input); - ArrayList<String> result = new ArrayList<>(); - - mChunker.chunkify(inputStream, (chunk) -> result.add(new String(chunk, UTF_8))); - - assertThat(result) - .containsExactly(CHUNK_1 + CHUNK_BOUNDARY, CHUNK_2 + CHUNK_BOUNDARY, CHUNK_3) - .inOrder(); - } - - /** Chunks should be combined across boundaries until they reach the minimum chunk size. */ - @Test - public void chunkify_withSmallChunks_combinesChunksUntilMinSize() throws Exception { - byte[] input = - (SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2 + CHUNK_BOUNDARY + CHUNK_3).getBytes(UTF_8); - ByteArrayInputStream inputStream = new ByteArrayInputStream(input); - ArrayList<String> result = new ArrayList<>(); - - mChunker.chunkify(inputStream, (chunk) -> result.add(new String(chunk, UTF_8))); - - assertThat(result) - .containsExactly(SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2 + CHUNK_BOUNDARY, CHUNK_3) - .inOrder(); - assertThat(result.get(0).length()).isAtLeast(MIN_SIZE_BYTES); - } - - /** Chunks can not be larger than the maximum chunk size. */ - @Test - public void chunkify_doesNotProduceChunksLargerThanMaxSize() throws Exception { - byte[] largeInput = new byte[MAX_SIZE_BYTES * 10]; - Arrays.fill(largeInput, "a".getBytes(UTF_8)[0]); - ByteArrayInputStream inputStream = new ByteArrayInputStream(largeInput); - ArrayList<String> result = new ArrayList<>(); - - mChunker.chunkify(inputStream, (chunk) -> result.add(new String(chunk, UTF_8))); - - byte[] expectedChunkBytes = new byte[MAX_SIZE_BYTES]; - Arrays.fill(expectedChunkBytes, "a".getBytes(UTF_8)[0]); - String expectedChunk = new String(expectedChunkBytes, UTF_8); - assertThat(result) - .containsExactly( - expectedChunk, - expectedChunk, - expectedChunk, - expectedChunk, - expectedChunk, - expectedChunk, - expectedChunk, - expectedChunk, - expectedChunk, - expectedChunk) - .inOrder(); - } - - /** - * If the input stream signals zero availablility, {@link - * ContentDefinedChunker#chunkify(InputStream, Chunker.ChunkConsumer)} should still work. - */ - @Test - public void chunkify_withInputStreamReturningZeroAvailability_returnsChunks() throws Exception { - byte[] input = (SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2).getBytes(UTF_8); - ZeroAvailabilityInputStream zeroAvailabilityInputStream = - new ZeroAvailabilityInputStream(input); - ArrayList<String> result = new ArrayList<>(); - - mChunker.chunkify( - zeroAvailabilityInputStream, (chunk) -> result.add(new String(chunk, UTF_8))); - - assertThat(result).containsExactly(SMALL_CHUNK + CHUNK_BOUNDARY + CHUNK_2).inOrder(); - } - - /** - * {@link ContentDefinedChunker#chunkify(InputStream, Chunker.ChunkConsumer)} should rethrow any - * exception thrown by its consumer. - */ - @Test - public void chunkify_whenConsumerThrowsException_rethrowsException() throws Exception { - ByteArrayInputStream inputStream = new ByteArrayInputStream(new byte[] {1}); - - assertThrows( - GeneralSecurityException.class, - () -> - mChunker.chunkify( - inputStream, - (chunk) -> { - throw new GeneralSecurityException(); - })); - } - - private long calculateFingerprint(byte[] bytes) { - long fingerprint = 0; - for (byte inByte : bytes) { - fingerprint = - mRabinFingerprint64.computeFingerprint64( - /*inChar=*/ inByte, /*outChar=*/ (byte) 0, fingerprint); - } - return mFingerprintMixer.mix(fingerprint); - } - - private static class ZeroAvailabilityInputStream extends ByteArrayInputStream { - ZeroAvailabilityInputStream(byte[] wrapped) { - super(wrapped); - } - - @Override - public synchronized int available() { - return 0; - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java deleted file mode 100644 index 936b5dca033d..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/FingerprintMixerTest.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.security.InvalidKeyException; -import java.security.Key; -import java.util.HashSet; -import java.util.Random; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -/** Tests for {@link FingerprintMixer}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class FingerprintMixerTest { - private static final String KEY_ALGORITHM = "AES"; - private static final int SEED = 42; - private static final int SALT_LENGTH_BYTES = 256 / 8; - private static final int KEY_SIZE_BITS = 256; - - private Random mSeededRandom; - private FingerprintMixer mFingerprintMixer; - - /** Set up a {@link FingerprintMixer} with deterministic key and salt generation. */ - @Before - public void setUp() throws Exception { - // Seed so that the tests are deterministic. - mSeededRandom = new Random(SEED); - mFingerprintMixer = new FingerprintMixer(randomKey(), randomSalt()); - } - - /** - * Construcing a {@link FingerprintMixer} with a salt that is too small should throw an {@link - * IllegalArgumentException}. - */ - @Test - public void create_withIncorrectSaltSize_throwsIllegalArgumentException() { - byte[] tooSmallSalt = new byte[SALT_LENGTH_BYTES - 1]; - - assertThrows( - IllegalArgumentException.class, - () -> new FingerprintMixer(randomKey(), tooSmallSalt)); - } - - /** - * Constructing a {@link FingerprintMixer} with a secret key that can't be encoded should throw - * an {@link InvalidKeyException}. - */ - @Test - public void create_withUnencodableSecretKey_throwsInvalidKeyException() { - byte[] keyBytes = new byte[KEY_SIZE_BITS / 8]; - UnencodableSecretKeySpec keySpec = - new UnencodableSecretKeySpec(keyBytes, 0, keyBytes.length, KEY_ALGORITHM); - - assertThrows(InvalidKeyException.class, () -> new FingerprintMixer(keySpec, randomSalt())); - } - - /** - * {@link FingerprintMixer#getAddend()} should not return the same addend for two different - * keys. - */ - @Test - public void getAddend_withDifferentKey_returnsDifferentResult() throws Exception { - int iterations = 100_000; - HashSet<Long> returnedAddends = new HashSet<>(); - byte[] salt = randomSalt(); - - for (int i = 0; i < iterations; i++) { - FingerprintMixer fingerprintMixer = new FingerprintMixer(randomKey(), salt); - long addend = fingerprintMixer.getAddend(); - returnedAddends.add(addend); - } - - assertThat(returnedAddends).containsNoDuplicates(); - } - - /** - * {@link FingerprintMixer#getMultiplicand()} should not return the same multiplicand for two - * different keys. - */ - @Test - public void getMultiplicand_withDifferentKey_returnsDifferentResult() throws Exception { - int iterations = 100_000; - HashSet<Long> returnedMultiplicands = new HashSet<>(); - byte[] salt = randomSalt(); - - for (int i = 0; i < iterations; i++) { - FingerprintMixer fingerprintMixer = new FingerprintMixer(randomKey(), salt); - long multiplicand = fingerprintMixer.getMultiplicand(); - returnedMultiplicands.add(multiplicand); - } - - assertThat(returnedMultiplicands).containsNoDuplicates(); - } - - /** The multiplicant returned by {@link FingerprintMixer} should always be odd. */ - @Test - public void getMultiplicand_isOdd() throws Exception { - int iterations = 100_000; - - for (int i = 0; i < iterations; i++) { - FingerprintMixer fingerprintMixer = new FingerprintMixer(randomKey(), randomSalt()); - - long multiplicand = fingerprintMixer.getMultiplicand(); - - assertThat(isOdd(multiplicand)).isTrue(); - } - } - - /** {@link FingerprintMixer#mix(long)} should have a random distribution. */ - @Test - public void mix_randomlyDistributesBits() throws Exception { - int iterations = 100_000; - float tolerance = 0.1f; - int[] totals = new int[64]; - - for (int i = 0; i < iterations; i++) { - long n = mFingerprintMixer.mix(mSeededRandom.nextLong()); - for (int j = 0; j < 64; j++) { - int bit = (int) (n >> j & 1); - totals[j] += bit; - } - } - - for (int i = 0; i < 64; i++) { - float mean = ((float) totals[i]) / iterations; - float diff = Math.abs(mean - 0.5f); - assertThat(diff).isLessThan(tolerance); - } - } - - /** - * {@link FingerprintMixer#mix(long)} should always produce a number that's different from the - * input. - */ - @Test - public void mix_doesNotProduceSameNumberAsInput() { - int iterations = 100_000; - - for (int i = 0; i < iterations; i++) { - assertThat(mFingerprintMixer.mix(i)).isNotEqualTo(i); - } - } - - private byte[] randomSalt() { - byte[] salt = new byte[SALT_LENGTH_BYTES]; - mSeededRandom.nextBytes(salt); - return salt; - } - - /** - * Not a secure way of generating keys. We want to deterministically generate the same keys for - * each test run, though, to ensure the test is deterministic. - */ - private SecretKey randomKey() { - byte[] keyBytes = new byte[KEY_SIZE_BITS / 8]; - mSeededRandom.nextBytes(keyBytes); - return new SecretKeySpec(keyBytes, 0, keyBytes.length, KEY_ALGORITHM); - } - - private static boolean isOdd(long n) { - return Math.abs(n % 2) == 1; - } - - /** - * Subclass of {@link SecretKeySpec} that does not provide an encoded version. As per its - * contract in {@link Key}, that means {@code getEncoded()} always returns null. - */ - private class UnencodableSecretKeySpec extends SecretKeySpec { - UnencodableSecretKeySpec(byte[] key, int offset, int len, String algorithm) { - super(key, offset, len, algorithm); - } - - @Override - public byte[] getEncoded() { - return null; - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java deleted file mode 100644 index 549437454e9c..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/HkdfTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -/** Tests for {@link Hkdf}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class HkdfTest { - /** HKDF Test Case 1 IKM from RFC 5869 */ - private static final byte[] HKDF_CASE1_IKM = { - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, - 0x0b, 0x0b - }; - - /** HKDF Test Case 1 salt from RFC 5869 */ - private static final byte[] HKDF_CASE1_SALT = { - 0x00, 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x07, 0x08, 0x09, - 0x0a, 0x0b, 0x0c - }; - - /** HKDF Test Case 1 info from RFC 5869 */ - private static final byte[] HKDF_CASE1_INFO = { - (byte) 0xf0, (byte) 0xf1, (byte) 0xf2, (byte) 0xf3, (byte) 0xf4, - (byte) 0xf5, (byte) 0xf6, (byte) 0xf7, (byte) 0xf8, (byte) 0xf9 - }; - - /** First 32 bytes of HKDF Test Case 1 OKM (output) from RFC 5869 */ - private static final byte[] HKDF_CASE1_OKM = { - (byte) 0x3c, (byte) 0xb2, (byte) 0x5f, (byte) 0x25, (byte) 0xfa, - (byte) 0xac, (byte) 0xd5, (byte) 0x7a, (byte) 0x90, (byte) 0x43, - (byte) 0x4f, (byte) 0x64, (byte) 0xd0, (byte) 0x36, (byte) 0x2f, - (byte) 0x2a, (byte) 0x2d, (byte) 0x2d, (byte) 0x0a, (byte) 0x90, - (byte) 0xcf, (byte) 0x1a, (byte) 0x5a, (byte) 0x4c, (byte) 0x5d, - (byte) 0xb0, (byte) 0x2d, (byte) 0x56, (byte) 0xec, (byte) 0xc4, - (byte) 0xc5, (byte) 0xbf - }; - - /** Test the example from RFC 5869. */ - @Test - public void hkdf_derivesKeyMaterial() throws Exception { - byte[] result = Hkdf.hkdf(HKDF_CASE1_IKM, HKDF_CASE1_SALT, HKDF_CASE1_INFO); - - assertThat(result).isEqualTo(HKDF_CASE1_OKM); - } - - /** Providing a key that is null should throw a {@link java.lang.NullPointerException}. */ - @Test - public void hkdf_withNullKey_throwsNullPointerException() throws Exception { - assertThrows( - NullPointerException.class, - () -> Hkdf.hkdf(null, HKDF_CASE1_SALT, HKDF_CASE1_INFO)); - } - - /** Providing a salt that is null should throw a {@link java.lang.NullPointerException}. */ - @Test - public void hkdf_withNullSalt_throwsNullPointerException() throws Exception { - assertThrows( - NullPointerException.class, () -> Hkdf.hkdf(HKDF_CASE1_IKM, null, HKDF_CASE1_INFO)); - } - - /** Providing data that is null should throw a {@link java.lang.NullPointerException}. */ - @Test - public void hkdf_withNullData_throwsNullPointerException() throws Exception { - assertThrows( - NullPointerException.class, () -> Hkdf.hkdf(HKDF_CASE1_IKM, HKDF_CASE1_SALT, null)); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java deleted file mode 100644 index 277dc372e73c..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/IsChunkBreakpointTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.util.Random; - -/** Tests for {@link IsChunkBreakpoint}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class IsChunkBreakpointTest { - private static final int RANDOM_SEED = 42; - private static final double TOLERANCE = 0.01; - private static final int NUMBER_OF_TESTS = 10000; - private static final int BITS_PER_LONG = 64; - - private Random mRandom; - - /** Make sure that tests are deterministic. */ - @Before - public void setUp() { - mRandom = new Random(RANDOM_SEED); - } - - /** - * Providing a negative average number of trials should throw an {@link - * IllegalArgumentException}. - */ - @Test - public void create_withNegativeAverageNumberOfTrials_throwsIllegalArgumentException() { - assertThrows(IllegalArgumentException.class, () -> new IsChunkBreakpoint(-1)); - } - - // Note: the following three tests are compute-intensive, so be cautious adding more. - - /** - * If the provided average number of trials is zero, a breakpoint should be expected after one - * trial on average. - */ - @Test - public void - isBreakpoint_withZeroAverageNumberOfTrials_isTrueOnAverageAfterOneTrial() { - assertExpectedTrials(new IsChunkBreakpoint(0), /*expectedTrials=*/ 1); - } - - /** - * If the provided average number of trials is 512, a breakpoint should be expected after 512 - * trials on average. - */ - @Test - public void - isBreakpoint_with512AverageNumberOfTrials_isTrueOnAverageAfter512Trials() { - assertExpectedTrials(new IsChunkBreakpoint(512), /*expectedTrials=*/ 512); - } - - /** - * If the provided average number of trials is 1024, a breakpoint should be expected after 1024 - * trials on average. - */ - @Test - public void - isBreakpoint_with1024AverageNumberOfTrials_isTrueOnAverageAfter1024Trials() { - assertExpectedTrials(new IsChunkBreakpoint(1024), /*expectedTrials=*/ 1024); - } - - /** The number of leading zeros should be the logarithm of the average number of trials. */ - @Test - public void getLeadingZeros_squaredIsAverageNumberOfTrials() { - for (int i = 0; i < BITS_PER_LONG; i++) { - long averageNumberOfTrials = (long) Math.pow(2, i); - - int leadingZeros = new IsChunkBreakpoint(averageNumberOfTrials).getLeadingZeros(); - - assertThat(leadingZeros).isEqualTo(i); - } - } - - private void assertExpectedTrials(IsChunkBreakpoint isChunkBreakpoint, long expectedTrials) { - long sum = 0; - for (int i = 0; i < NUMBER_OF_TESTS; i++) { - sum += numberOfTrialsTillBreakpoint(isChunkBreakpoint); - } - long averageTrials = sum / NUMBER_OF_TESTS; - assertThat((double) Math.abs(averageTrials - expectedTrials)) - .isLessThan(TOLERANCE * expectedTrials); - } - - private int numberOfTrialsTillBreakpoint(IsChunkBreakpoint isChunkBreakpoint) { - int trials = 0; - - while (true) { - trials++; - if (isChunkBreakpoint.isBreakpoint(mRandom.nextLong())) { - return trials; - } - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java deleted file mode 100644 index 729580cf5101..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/chunking/cdc/RabinFingerprint64Test.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2018 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.chunking.cdc; - -import static com.google.common.truth.Truth.assertThat; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import android.platform.test.annotations.Presubmit; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -/** Tests for {@link RabinFingerprint64}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class RabinFingerprint64Test { - private static final int WINDOW_SIZE = 31; - private static final ImmutableList<String> TEST_STRINGS = - ImmutableList.of( - "ervHTtChYXO6eXivYqThlyyzqkbRaOR", - "IxaVunH9ZC3qneWfhj1GkBH4ys9CYqz", - "wZRVjlE1p976icCFPX9pibk4PEBvjSH", - "pHIVaT8x8If9D6s9croksgNmJpmGYWI"); - - private final RabinFingerprint64 mRabinFingerprint64 = new RabinFingerprint64(); - - /** - * No matter where in the input buffer a string occurs, {@link - * RabinFingerprint64#computeFingerprint64(byte, byte, long)} should return the same - * fingerprint. - */ - @Test - public void computeFingerprint64_forSameWindow_returnsSameFingerprint() { - long fingerprint1 = - computeFingerprintAtPosition(getBytes(TEST_STRINGS.get(0)), WINDOW_SIZE - 1); - long fingerprint2 = - computeFingerprintAtPosition( - getBytes(TEST_STRINGS.get(1), TEST_STRINGS.get(0)), WINDOW_SIZE * 2 - 1); - long fingerprint3 = - computeFingerprintAtPosition( - getBytes(TEST_STRINGS.get(2), TEST_STRINGS.get(3), TEST_STRINGS.get(0)), - WINDOW_SIZE * 3 - 1); - String stub = "abc"; - long fingerprint4 = - computeFingerprintAtPosition( - getBytes(stub, TEST_STRINGS.get(0)), WINDOW_SIZE + stub.length() - 1); - - // Assert that all fingerprints are exactly the same - assertThat(ImmutableSet.of(fingerprint1, fingerprint2, fingerprint3, fingerprint4)) - .hasSize(1); - } - - /** The computed fingerprint should be different for different inputs. */ - @Test - public void computeFingerprint64_withDifferentInput_returnsDifferentFingerprint() { - long fingerprint1 = computeFingerprintOf(TEST_STRINGS.get(0)); - long fingerprint2 = computeFingerprintOf(TEST_STRINGS.get(1)); - long fingerprint3 = computeFingerprintOf(TEST_STRINGS.get(2)); - long fingerprint4 = computeFingerprintOf(TEST_STRINGS.get(3)); - - assertThat(ImmutableList.of(fingerprint1, fingerprint2, fingerprint3, fingerprint4)) - .containsNoDuplicates(); - } - - /** - * An input with the same characters in a different order should return a different fingerprint. - */ - @Test - public void computeFingerprint64_withSameInputInDifferentOrder_returnsDifferentFingerprint() { - long fingerprint1 = computeFingerprintOf("abcdefghijklmnopqrstuvwxyz12345"); - long fingerprint2 = computeFingerprintOf("54321zyxwvutsrqponmlkjihgfedcba"); - long fingerprint3 = computeFingerprintOf("4bcdefghijklmnopqrstuvwxyz123a5"); - long fingerprint4 = computeFingerprintOf("bacdefghijklmnopqrstuvwxyz12345"); - - assertThat(ImmutableList.of(fingerprint1, fingerprint2, fingerprint3, fingerprint4)) - .containsNoDuplicates(); - } - - /** UTF-8 bytes of all the given strings in order. */ - private byte[] getBytes(String... strings) { - StringBuilder sb = new StringBuilder(); - for (String s : strings) { - sb.append(s); - } - return sb.toString().getBytes(UTF_8); - } - - /** - * The Rabin fingerprint of a window of bytes ending at {@code position} in the {@code bytes} - * array. - */ - private long computeFingerprintAtPosition(byte[] bytes, int position) { - assertThat(position).isAtMost(bytes.length - 1); - long fingerprint = 0; - for (int i = 0; i <= position; i++) { - byte outChar; - if (i >= WINDOW_SIZE) { - outChar = bytes[i - WINDOW_SIZE]; - } else { - outChar = (byte) 0; - } - fingerprint = - mRabinFingerprint64.computeFingerprint64( - /*inChar=*/ bytes[i], outChar, fingerprint); - } - return fingerprint; - } - - private long computeFingerprintOf(String s) { - assertThat(s.length()).isEqualTo(WINDOW_SIZE); - return computeFingerprintAtPosition(s.getBytes(UTF_8), WINDOW_SIZE - 1); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java deleted file mode 100644 index b60740421ad5..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/KeyWrapUtilsTest.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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.keys; - -import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.security.InvalidKeyException; - -import javax.crypto.SecretKey; - -/** Key wrapping tests */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class KeyWrapUtilsTest { - private static final int KEY_SIZE_BITS = 256; - private static final int BITS_PER_BYTE = 8; - private static final int GCM_NONCE_LENGTH_BYTES = 16; - private static final int GCM_TAG_LENGTH_BYTES = 16; - - /** Test a wrapped key has metadata */ - @Test - public void wrap_addsMetadata() throws Exception { - WrappedKeyProto.WrappedKey wrappedKey = - KeyWrapUtils.wrap( - /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey()); - assertThat(wrappedKey.metadata).isNotNull(); - assertThat(wrappedKey.metadata.type).isEqualTo(WrappedKeyProto.KeyMetadata.AES_256_GCM); - } - - /** Test a wrapped key has an algorithm specified */ - @Test - public void wrap_addsWrapAlgorithm() throws Exception { - WrappedKeyProto.WrappedKey wrappedKey = - KeyWrapUtils.wrap( - /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey()); - assertThat(wrappedKey.wrapAlgorithm).isEqualTo(WrappedKeyProto.WrappedKey.AES_256_GCM); - } - - /** Test a wrapped key haas an nonce of the right length */ - @Test - public void wrap_addsNonceOfAppropriateLength() throws Exception { - WrappedKeyProto.WrappedKey wrappedKey = - KeyWrapUtils.wrap( - /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey()); - assertThat(wrappedKey.nonce).hasLength(GCM_NONCE_LENGTH_BYTES); - } - - /** Test a wrapped key has a key of the right length */ - @Test - public void wrap_addsTagOfAppropriateLength() throws Exception { - WrappedKeyProto.WrappedKey wrappedKey = - KeyWrapUtils.wrap( - /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey()); - assertThat(wrappedKey.key).hasLength(KEY_SIZE_BITS / BITS_PER_BYTE + GCM_TAG_LENGTH_BYTES); - } - - /** Ensure a key can be wrapped and unwrapped again */ - @Test - public void unwrap_unwrapsEncryptedKey() throws Exception { - SecretKey secondaryKey = generateAesKey(); - SecretKey tertiaryKey = generateAesKey(); - WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, tertiaryKey); - SecretKey unwrappedKey = KeyWrapUtils.unwrap(secondaryKey, wrappedKey); - assertThat(unwrappedKey).isEqualTo(tertiaryKey); - } - - /** Ensure the unwrap method rejects keys with bad algorithms */ - @Test(expected = InvalidKeyException.class) - public void unwrap_throwsForBadWrapAlgorithm() throws Exception { - SecretKey secondaryKey = generateAesKey(); - WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey()); - wrappedKey.wrapAlgorithm = WrappedKeyProto.WrappedKey.UNKNOWN; - - KeyWrapUtils.unwrap(secondaryKey, wrappedKey); - } - - /** Ensure the unwrap method rejects metadata indicating the encryption type is unknown */ - @Test(expected = InvalidKeyException.class) - public void unwrap_throwsForBadKeyAlgorithm() throws Exception { - SecretKey secondaryKey = generateAesKey(); - WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey()); - wrappedKey.metadata.type = WrappedKeyProto.KeyMetadata.UNKNOWN; - - KeyWrapUtils.unwrap(secondaryKey, wrappedKey); - } - - /** Ensure the unwrap method rejects wrapped keys missing the metadata */ - @Test(expected = InvalidKeyException.class) - public void unwrap_throwsForMissingMetadata() throws Exception { - SecretKey secondaryKey = generateAesKey(); - WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(secondaryKey, generateAesKey()); - wrappedKey.metadata = null; - - KeyWrapUtils.unwrap(secondaryKey, wrappedKey); - } - - /** Ensure unwrap rejects invalid secondary keys */ - @Test(expected = InvalidKeyException.class) - public void unwrap_throwsForBadSecondaryKey() throws Exception { - WrappedKeyProto.WrappedKey wrappedKey = - KeyWrapUtils.wrap( - /*secondaryKey=*/ generateAesKey(), /*tertiaryKey=*/ generateAesKey()); - - KeyWrapUtils.unwrap(generateAesKey(), wrappedKey); - } - - /** Ensure rewrap can rewrap keys */ - @Test - public void rewrap_canBeUnwrappedWithNewSecondaryKey() throws Exception { - SecretKey tertiaryKey = generateAesKey(); - SecretKey oldSecondaryKey = generateAesKey(); - SecretKey newSecondaryKey = generateAesKey(); - WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey); - - WrappedKeyProto.WrappedKey wrappedWithNew = - KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld); - - assertThat(KeyWrapUtils.unwrap(newSecondaryKey, wrappedWithNew)).isEqualTo(tertiaryKey); - } - - /** Ensure rewrap doesn't create something decryptable by an old key */ - @Test(expected = InvalidKeyException.class) - public void rewrap_cannotBeUnwrappedWithOldSecondaryKey() throws Exception { - SecretKey tertiaryKey = generateAesKey(); - SecretKey oldSecondaryKey = generateAesKey(); - SecretKey newSecondaryKey = generateAesKey(); - WrappedKeyProto.WrappedKey wrappedWithOld = KeyWrapUtils.wrap(oldSecondaryKey, tertiaryKey); - - WrappedKeyProto.WrappedKey wrappedWithNew = - KeyWrapUtils.rewrap(oldSecondaryKey, newSecondaryKey, wrappedWithOld); - - KeyWrapUtils.unwrap(oldSecondaryKey, wrappedWithNew); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java deleted file mode 100644 index 5342efa18a97..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyManagerTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2018 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.keys; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.content.Context; -import android.platform.test.annotations.Presubmit; -import android.security.keystore.recovery.InternalRecoveryServiceException; -import android.security.keystore.recovery.RecoveryController; - -import com.android.server.testing.shadows.ShadowInternalRecoveryServiceException; -import com.android.server.testing.shadows.ShadowRecoveryController; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import java.security.SecureRandom; -import java.util.Optional; - -/** Tests for {@link RecoverableKeyStoreSecondaryKeyManager}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -@Config(shadows = {ShadowRecoveryController.class, ShadowInternalRecoveryServiceException.class}) -public class RecoverableKeyStoreSecondaryKeyManagerTest { - private static final String BACKUP_KEY_ALIAS_PREFIX = - "com.android.server.backup/recoverablekeystore/"; - private static final int BITS_PER_BYTE = 8; - private static final int BACKUP_KEY_SUFFIX_LENGTH_BYTES = 128 / BITS_PER_BYTE; - private static final int HEX_PER_BYTE = 2; - private static final int BACKUP_KEY_ALIAS_LENGTH = - BACKUP_KEY_ALIAS_PREFIX.length() + BACKUP_KEY_SUFFIX_LENGTH_BYTES * HEX_PER_BYTE; - private static final String NONEXISTENT_KEY_ALIAS = "NONEXISTENT_KEY_ALIAS"; - - private RecoverableKeyStoreSecondaryKeyManager mRecoverableKeyStoreSecondaryKeyManager; - private Context mContext; - - /** Create a new {@link RecoverableKeyStoreSecondaryKeyManager} to use in tests. */ - @Before - public void setUp() throws Exception { - mContext = RuntimeEnvironment.application; - - mRecoverableKeyStoreSecondaryKeyManager = - new RecoverableKeyStoreSecondaryKeyManager( - RecoveryController.getInstance(mContext), new SecureRandom()); - } - - /** Reset the {@link ShadowRecoveryController}. */ - @After - public void tearDown() throws Exception { - ShadowRecoveryController.reset(); - } - - /** The generated key should always have the prefix {@code BACKUP_KEY_ALIAS_PREFIX}. */ - @Test - public void generate_generatesKeyWithExpectedPrefix() throws Exception { - RecoverableKeyStoreSecondaryKey key = mRecoverableKeyStoreSecondaryKeyManager.generate(); - - assertThat(key.getAlias()).startsWith(BACKUP_KEY_ALIAS_PREFIX); - } - - /** The generated key should always have length {@code BACKUP_KEY_ALIAS_LENGTH}. */ - @Test - public void generate_generatesKeyWithExpectedLength() throws Exception { - RecoverableKeyStoreSecondaryKey key = mRecoverableKeyStoreSecondaryKeyManager.generate(); - - assertThat(key.getAlias()).hasLength(BACKUP_KEY_ALIAS_LENGTH); - } - - /** Ensure that hidden API exceptions are rethrown when generating keys. */ - @Test - public void generate_encounteringHiddenApiException_rethrowsException() { - ShadowRecoveryController.setThrowsInternalError(true); - - assertThrows( - InternalRecoveryServiceException.class, - mRecoverableKeyStoreSecondaryKeyManager::generate); - } - - /** Ensure that retrieved keys correspond to those generated earlier. */ - @Test - public void get_getsKeyGeneratedByController() throws Exception { - RecoverableKeyStoreSecondaryKey key = mRecoverableKeyStoreSecondaryKeyManager.generate(); - - Optional<RecoverableKeyStoreSecondaryKey> retrievedKey = - mRecoverableKeyStoreSecondaryKeyManager.get(key.getAlias()); - - assertThat(retrievedKey.isPresent()).isTrue(); - assertThat(retrievedKey.get().getAlias()).isEqualTo(key.getAlias()); - assertThat(retrievedKey.get().getSecretKey()).isEqualTo(key.getSecretKey()); - } - - /** - * Ensure that a call to {@link RecoverableKeyStoreSecondaryKeyManager#get(java.lang.String)} - * for nonexistent aliases returns an emtpy {@link Optional}. - */ - @Test - public void get_forNonExistentKey_returnsEmptyOptional() throws Exception { - Optional<RecoverableKeyStoreSecondaryKey> retrievedKey = - mRecoverableKeyStoreSecondaryKeyManager.get(NONEXISTENT_KEY_ALIAS); - - assertThat(retrievedKey.isPresent()).isFalse(); - } - - /** - * Ensure that exceptions occurring during {@link - * RecoverableKeyStoreSecondaryKeyManager#get(java.lang.String)} are not rethrown. - */ - @Test - public void get_encounteringInternalException_doesNotPropagateException() throws Exception { - ShadowRecoveryController.setThrowsInternalError(true); - - // Should not throw exception - mRecoverableKeyStoreSecondaryKeyManager.get(NONEXISTENT_KEY_ALIAS); - } - - /** Ensure that keys are correctly removed from the store. */ - @Test - public void remove_removesKeyFromRecoverableStore() throws Exception { - RecoverableKeyStoreSecondaryKey key = mRecoverableKeyStoreSecondaryKeyManager.generate(); - - mRecoverableKeyStoreSecondaryKeyManager.remove(key.getAlias()); - - assertThat(RecoveryController.getInstance(mContext).getAliases()) - .doesNotContain(key.getAlias()); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java deleted file mode 100644 index 89977f82c145..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKeyTest.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (C) 2018 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.keys; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.content.Context; -import android.platform.test.annotations.Presubmit; -import android.security.keystore.recovery.RecoveryController; - -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey.Status; -import com.android.server.backup.testing.CryptoTestUtils; -import com.android.server.testing.shadows.ShadowInternalRecoveryServiceException; -import com.android.server.testing.shadows.ShadowRecoveryController; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import javax.crypto.SecretKey; - -/** Tests for {@link RecoverableKeyStoreSecondaryKey}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -@Config(shadows = {ShadowRecoveryController.class, ShadowInternalRecoveryServiceException.class}) -public class RecoverableKeyStoreSecondaryKeyTest { - private static final String TEST_ALIAS = "test"; - private static final int NONEXISTENT_STATUS_CODE = 42; - - private RecoverableKeyStoreSecondaryKey mSecondaryKey; - private SecretKey mGeneratedSecretKey; - private Context mContext; - - /** Instantiate a {@link RecoverableKeyStoreSecondaryKey} to use in tests. */ - @Before - public void setUp() throws Exception { - mContext = RuntimeEnvironment.application; - mGeneratedSecretKey = CryptoTestUtils.generateAesKey(); - mSecondaryKey = new RecoverableKeyStoreSecondaryKey(TEST_ALIAS, mGeneratedSecretKey); - } - - /** Reset the {@link ShadowRecoveryController}. */ - @After - public void tearDown() throws Exception { - ShadowRecoveryController.reset(); - } - - /** - * Checks that {@link RecoverableKeyStoreSecondaryKey#getAlias()} returns the value supplied in - * the constructor. - */ - @Test - public void getAlias() { - String alias = mSecondaryKey.getAlias(); - - assertThat(alias).isEqualTo(TEST_ALIAS); - } - - /** - * Checks that {@link RecoverableKeyStoreSecondaryKey#getSecretKey()} returns the value supplied - * in the constructor. - */ - @Test - public void getSecretKey() { - SecretKey secretKey = mSecondaryKey.getSecretKey(); - - assertThat(secretKey).isEqualTo(mGeneratedSecretKey); - } - - /** - * Checks that passing a secret key that is null to the constructor throws an exception. - */ - @Test - public void constructor_withNullSecretKey_throwsNullPointerException() { - assertThrows( - NullPointerException.class, - () -> new RecoverableKeyStoreSecondaryKey(TEST_ALIAS, null)); - } - - /** - * Checks that passing an alias that is null to the constructor throws an exception. - */ - @Test - public void constructor_withNullAlias_throwsNullPointerException() { - assertThrows( - NullPointerException.class, - () -> new RecoverableKeyStoreSecondaryKey(null, mGeneratedSecretKey)); - } - - /** Checks that the synced status is returned correctly. */ - @Test - public void getStatus_whenSynced_returnsSynced() throws Exception { - setStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - - int status = mSecondaryKey.getStatus(mContext); - - assertThat(status).isEqualTo(Status.SYNCED); - } - - /** Checks that the in progress sync status is returned correctly. */ - @Test - public void getStatus_whenNotSynced_returnsNotSynced() throws Exception { - setStatus(RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS); - - int status = mSecondaryKey.getStatus(mContext); - - assertThat(status).isEqualTo(Status.NOT_SYNCED); - } - - /** Checks that the failure status is returned correctly. */ - @Test - public void getStatus_onPermanentFailure_returnsDestroyed() throws Exception { - setStatus(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE); - - int status = mSecondaryKey.getStatus(mContext); - - assertThat(status).isEqualTo(Status.DESTROYED); - } - - /** Checks that an unknown status results in {@code NOT_SYNCED} being returned. */ - @Test - public void getStatus_forUnknownStatusCode_returnsNotSynced() throws Exception { - setStatus(NONEXISTENT_STATUS_CODE); - - int status = mSecondaryKey.getStatus(mContext); - - assertThat(status).isEqualTo(Status.NOT_SYNCED); - } - - /** Checks that an internal error results in {@code NOT_SYNCED} being returned. */ - @Test - public void getStatus_onInternalError_returnsNotSynced() throws Exception { - ShadowRecoveryController.setThrowsInternalError(true); - - int status = mSecondaryKey.getStatus(mContext); - - assertThat(status).isEqualTo(Status.NOT_SYNCED); - } - - private void setStatus(int status) throws Exception { - ShadowRecoveryController.setRecoveryStatus(TEST_ALIAS, status); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RestoreKeyFetcherTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RestoreKeyFetcherTest.java deleted file mode 100644 index 004f8097ce39..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/RestoreKeyFetcherTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.keys; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; - -import java.security.InvalidKeyException; -import java.security.KeyException; -import java.security.SecureRandom; -import java.util.Optional; - -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -/** Test the restore key fetcher */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class RestoreKeyFetcherTest { - - private static final String KEY_GENERATOR_ALGORITHM = "AES"; - - private static final String TEST_SECONDARY_KEY_ALIAS = "test_2ndary_key"; - private static final byte[] TEST_SECONDARY_KEY_BYTES = new byte[256 / Byte.SIZE]; - - @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; - - /** Initialise the mocks **/ - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - } - - /** Ensure the unwrap method works as expected */ - @Test - public void unwrapTertiaryKey_returnsUnwrappedKey() throws Exception { - RecoverableKeyStoreSecondaryKey secondaryKey = createSecondaryKey(); - SecretKey tertiaryKey = createTertiaryKey(); - WrappedKeyProto.WrappedKey wrappedTertiaryKey = - KeyWrapUtils.wrap(secondaryKey.getSecretKey(), tertiaryKey); - when(mSecondaryKeyManager.get(TEST_SECONDARY_KEY_ALIAS)) - .thenReturn(Optional.of(secondaryKey)); - - SecretKey actualTertiaryKey = - RestoreKeyFetcher.unwrapTertiaryKey( - () -> mSecondaryKeyManager, - TEST_SECONDARY_KEY_ALIAS, - wrappedTertiaryKey); - - assertThat(actualTertiaryKey).isEqualTo(tertiaryKey); - } - - /** Ensure that missing secondary keys are detected and an appropriate exception is thrown */ - @Test - public void unwrapTertiaryKey_missingSecondaryKey_throwsSpecificException() throws Exception { - WrappedKeyProto.WrappedKey wrappedTertiaryKey = - KeyWrapUtils.wrap(createSecondaryKey().getSecretKey(), createTertiaryKey()); - when(mSecondaryKeyManager.get(TEST_SECONDARY_KEY_ALIAS)).thenReturn(Optional.empty()); - - assertThrows( - KeyException.class, - () -> - RestoreKeyFetcher.unwrapTertiaryKey( - () -> mSecondaryKeyManager, - TEST_SECONDARY_KEY_ALIAS, - wrappedTertiaryKey)); - } - - /** Ensure that invalid secondary keys are detected and an appropriate exception is thrown */ - @Test - public void unwrapTertiaryKey_badSecondaryKey_throws() throws Exception { - RecoverableKeyStoreSecondaryKey badSecondaryKey = - new RecoverableKeyStoreSecondaryKey( - TEST_SECONDARY_KEY_ALIAS, - new SecretKeySpec(new byte[] {0, 1}, KEY_GENERATOR_ALGORITHM)); - - WrappedKeyProto.WrappedKey wrappedTertiaryKey = - KeyWrapUtils.wrap(createSecondaryKey().getSecretKey(), createTertiaryKey()); - when(mSecondaryKeyManager.get(TEST_SECONDARY_KEY_ALIAS)) - .thenReturn(Optional.of(badSecondaryKey)); - - assertThrows( - InvalidKeyException.class, - () -> - RestoreKeyFetcher.unwrapTertiaryKey( - () -> mSecondaryKeyManager, - TEST_SECONDARY_KEY_ALIAS, - wrappedTertiaryKey)); - } - - private static RecoverableKeyStoreSecondaryKey createSecondaryKey() { - return new RecoverableKeyStoreSecondaryKey( - TEST_SECONDARY_KEY_ALIAS, - new SecretKeySpec(TEST_SECONDARY_KEY_BYTES, KEY_GENERATOR_ALGORITHM)); - } - - private static SecretKey createTertiaryKey() { - return new TertiaryKeyGenerator(new SecureRandom(new byte[] {0})).generate(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java deleted file mode 100644 index c31d19d8568c..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/SecondaryKeyRotationSchedulerTest.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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.keys; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.platform.test.annotations.Presubmit; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.server.backup.encryption.CryptoSettings; -import com.android.server.backup.encryption.tasks.StartSecondaryKeyRotationTask; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.Resetter; - -import java.io.File; -import java.time.Clock; - -@Config(shadows = SecondaryKeyRotationSchedulerTest.ShadowStartSecondaryKeyRotationTask.class) -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class SecondaryKeyRotationSchedulerTest { - private static final String SENTINEL_FILE_PATH = "force_secondary_key_rotation"; - - @Mock private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; - @Mock private Clock mClock; - - private CryptoSettings mCryptoSettings; - private SecondaryKeyRotationScheduler mScheduler; - private long mRotationIntervalMillis; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - Context application = ApplicationProvider.getApplicationContext(); - - mCryptoSettings = CryptoSettings.getInstanceForTesting(application); - mRotationIntervalMillis = mCryptoSettings.backupSecondaryKeyRotationIntervalMs(); - - mScheduler = - new SecondaryKeyRotationScheduler( - application, mSecondaryKeyManager, mCryptoSettings, mClock); - ShadowStartSecondaryKeyRotationTask.reset(); - } - - @Test - public void startRotationIfScheduled_rotatesIfRotationWasFarEnoughInThePast() { - long lastRotated = 100009; - mCryptoSettings.setSecondaryLastRotated(lastRotated); - setNow(lastRotated + mRotationIntervalMillis); - - mScheduler.startRotationIfScheduled(); - - assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); - } - - @Test - public void startRotationIfScheduled_setsNewRotationTimeIfRotationWasFarEnoughInThePast() { - long lastRotated = 100009; - long now = lastRotated + mRotationIntervalMillis; - mCryptoSettings.setSecondaryLastRotated(lastRotated); - setNow(now); - - mScheduler.startRotationIfScheduled(); - - assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); - } - - @Test - public void startRotationIfScheduled_rotatesIfClockHasChanged() { - long lastRotated = 100009; - mCryptoSettings.setSecondaryLastRotated(lastRotated); - setNow(lastRotated - 1); - - mScheduler.startRotationIfScheduled(); - - assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); - } - - @Test - public void startRotationIfScheduled_rotatesIfSentinelFileIsPresent() throws Exception { - File file = new File(RuntimeEnvironment.application.getFilesDir(), SENTINEL_FILE_PATH); - file.createNewFile(); - - mScheduler.startRotationIfScheduled(); - - assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isTrue(); - } - - @Test - public void startRotationIfScheduled_setsNextRotationIfClockHasChanged() { - long lastRotated = 100009; - long now = lastRotated - 1; - mCryptoSettings.setSecondaryLastRotated(lastRotated); - setNow(now); - - mScheduler.startRotationIfScheduled(); - - assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); - } - - @Test - public void startRotationIfScheduled_doesNothingIfRotationWasRecentEnough() { - long lastRotated = 100009; - mCryptoSettings.setSecondaryLastRotated(lastRotated); - setNow(lastRotated + mRotationIntervalMillis - 1); - - mScheduler.startRotationIfScheduled(); - - assertThat(ShadowStartSecondaryKeyRotationTask.sRan).isFalse(); - } - - @Test - public void startRotationIfScheduled_doesNotSetRotationTimeIfRotationWasRecentEnough() { - long lastRotated = 100009; - mCryptoSettings.setSecondaryLastRotated(lastRotated); - setNow(lastRotated + mRotationIntervalMillis - 1); - - mScheduler.startRotationIfScheduled(); - - assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(lastRotated); - } - - @Test - public void startRotationIfScheduled_setsLastRotatedToNowIfNeverRotated() { - long now = 13295436; - setNow(now); - - mScheduler.startRotationIfScheduled(); - - assertThat(mCryptoSettings.getSecondaryLastRotated().get()).isEqualTo(now); - } - - private void setNow(long timestamp) { - when(mClock.millis()).thenReturn(timestamp); - } - - @Implements(StartSecondaryKeyRotationTask.class) - public static class ShadowStartSecondaryKeyRotationTask { - private static boolean sRan = false; - - @Implementation - public void run() { - sRan = true; - } - - @Resetter - public static void reset() { - sRan = false; - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java deleted file mode 100644 index 48216f8d7aca..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyGeneratorTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2018 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.keys; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.security.SecureRandom; - -import javax.crypto.SecretKey; - -/** Tests for {@link TertiaryKeyGenerator}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class TertiaryKeyGeneratorTest { - private static final String KEY_ALGORITHM = "AES"; - private static final int KEY_SIZE_BITS = 256; - - private TertiaryKeyGenerator mTertiaryKeyGenerator; - - /** Instantiate a new {@link TertiaryKeyGenerator} for use in tests. */ - @Before - public void setUp() { - mTertiaryKeyGenerator = new TertiaryKeyGenerator(new SecureRandom()); - } - - /** Generated keys should be AES keys. */ - @Test - public void generate_generatesAESKeys() { - SecretKey secretKey = mTertiaryKeyGenerator.generate(); - - assertThat(secretKey.getAlgorithm()).isEqualTo(KEY_ALGORITHM); - } - - /** Generated keys should be 256 bits in size. */ - @Test - public void generate_generates256BitKeys() { - SecretKey secretKey = mTertiaryKeyGenerator.generate(); - - assertThat(secretKey.getEncoded()).hasLength(KEY_SIZE_BITS / 8); - } - - /** - * Subsequent calls to {@link TertiaryKeyGenerator#generate()} should generate different keys. - */ - @Test - public void generate_generatesNewKeys() { - SecretKey key1 = mTertiaryKeyGenerator.generate(); - SecretKey key2 = mTertiaryKeyGenerator.generate(); - - assertThat(key1).isNotEqualTo(key2); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyManagerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyManagerTest.java deleted file mode 100644 index 1ed8309c3a48..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyManagerTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * 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.keys; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.robolectric.RuntimeEnvironment.application; - -import android.security.keystore.recovery.RecoveryController; - -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; -import com.android.server.testing.shadows.ShadowRecoveryController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InOrder; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.security.SecureRandom; - -import javax.crypto.SecretKey; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = ShadowRecoveryController.class) -public class TertiaryKeyManagerTest { - - private static final String TEST_PACKAGE_1 = "com.example.app1"; - private static final String TEST_PACKAGE_2 = "com.example.app2"; - - private SecureRandom mSecureRandom; - private RecoverableKeyStoreSecondaryKey mSecondaryKey; - - @Mock private TertiaryKeyRotationScheduler mTertiaryKeyRotationScheduler; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mSecureRandom = new SecureRandom(); - mSecondaryKey = - new RecoverableKeyStoreSecondaryKeyManager( - RecoveryController.getInstance(application), mSecureRandom) - .generate(); - ShadowRecoveryController.reset(); - } - - private TertiaryKeyManager createNewManager(String packageName) { - return new TertiaryKeyManager( - application, - mSecureRandom, - mTertiaryKeyRotationScheduler, - mSecondaryKey, - packageName); - } - - @Test - public void getKey_noExistingKey_returnsNewKey() throws Exception { - assertThat(createNewManager(TEST_PACKAGE_1).getKey()).isNotNull(); - } - - @Test - public void getKey_noExistingKey_recordsIncrementalBackup() throws Exception { - createNewManager(TEST_PACKAGE_1).getKey(); - verify(mTertiaryKeyRotationScheduler).recordBackup(TEST_PACKAGE_1); - } - - @Test - public void getKey_existingKey_returnsExistingKey() throws Exception { - TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1); - SecretKey existingKey = manager.getKey(); - - assertThat(manager.getKey()).isEqualTo(existingKey); - } - - @Test - public void getKey_existingKey_recordsBackupButNotRotation() throws Exception { - createNewManager(TEST_PACKAGE_1).getKey(); - reset(mTertiaryKeyRotationScheduler); - - createNewManager(TEST_PACKAGE_1).getKey(); - - verify(mTertiaryKeyRotationScheduler).recordBackup(TEST_PACKAGE_1); - verify(mTertiaryKeyRotationScheduler, never()).recordKeyRotation(any()); - } - - @Test - public void getKey_existingKeyButRotationRequired_returnsNewKey() throws Exception { - SecretKey firstKey = createNewManager(TEST_PACKAGE_1).getKey(); - when(mTertiaryKeyRotationScheduler.isKeyRotationDue(TEST_PACKAGE_1)).thenReturn(true); - - SecretKey secondKey = createNewManager(TEST_PACKAGE_1).getKey(); - - assertThat(secondKey).isNotEqualTo(firstKey); - } - - @Test - public void getKey_existingKeyButRotationRequired_recordsKeyRotationAndBackup() - throws Exception { - when(mTertiaryKeyRotationScheduler.isKeyRotationDue(TEST_PACKAGE_1)).thenReturn(true); - createNewManager(TEST_PACKAGE_1).getKey(); - - InOrder inOrder = inOrder(mTertiaryKeyRotationScheduler); - inOrder.verify(mTertiaryKeyRotationScheduler).recordKeyRotation(TEST_PACKAGE_1); - inOrder.verify(mTertiaryKeyRotationScheduler).recordBackup(TEST_PACKAGE_1); - } - - @Test - public void getKey_twoApps_returnsDifferentKeys() throws Exception { - TertiaryKeyManager firstManager = createNewManager(TEST_PACKAGE_1); - TertiaryKeyManager secondManager = createNewManager(TEST_PACKAGE_2); - SecretKey firstKey = firstManager.getKey(); - - assertThat(secondManager.getKey()).isNotEqualTo(firstKey); - } - - @Test - public void getWrappedKey_noExistingKey_returnsWrappedNewKey() throws Exception { - TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1); - SecretKey unwrappedKey = manager.getKey(); - WrappedKeyProto.WrappedKey wrappedKey = manager.getWrappedKey(); - - SecretKey expectedUnwrappedKey = - KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey); - assertThat(unwrappedKey).isEqualTo(expectedUnwrappedKey); - } - - @Test - public void getWrappedKey_existingKey_returnsWrappedExistingKey() throws Exception { - TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1); - WrappedKeyProto.WrappedKey wrappedKey = manager.getWrappedKey(); - SecretKey unwrappedKey = manager.getKey(); - - SecretKey expectedUnwrappedKey = - KeyWrapUtils.unwrap(mSecondaryKey.getSecretKey(), wrappedKey); - assertThat(unwrappedKey).isEqualTo(expectedUnwrappedKey); - } - - @Test - public void wasKeyRotated_noExistingKey_returnsTrue() throws Exception { - TertiaryKeyManager manager = createNewManager(TEST_PACKAGE_1); - assertThat(manager.wasKeyRotated()).isTrue(); - } - - @Test - public void wasKeyRotated_existingKey_returnsFalse() throws Exception { - createNewManager(TEST_PACKAGE_1).getKey(); - assertThat(createNewManager(TEST_PACKAGE_1).wasKeyRotated()).isFalse(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationSchedulerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationSchedulerTest.java deleted file mode 100644 index dfc7e2bfd4f7..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationSchedulerTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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.keys; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.when; -import static org.robolectric.RuntimeEnvironment.application; - -import android.content.Context; - -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 org.robolectric.RobolectricTestRunner; - -import java.io.File; -import java.time.Clock; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.TimeUnit; - -/** Tests for the tertiary key rotation scheduler */ -@RunWith(RobolectricTestRunner.class) -public final class TertiaryKeyRotationSchedulerTest { - - private static final int MAXIMUM_ROTATIONS_PER_WINDOW = 2; - private static final int MAX_BACKUPS_TILL_ROTATION = 31; - private static final String SHARED_PREFS_NAME = "tertiary_key_rotation_tracker"; - private static final String PACKAGE_1 = "com.android.example1"; - private static final String PACKAGE_2 = "com.android.example2"; - - @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Mock private Clock mClock; - - private File mFile; - private TertiaryKeyRotationScheduler mScheduler; - - /** Setup the scheduler for test */ - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - mFile = temporaryFolder.newFile(); - mScheduler = - new TertiaryKeyRotationScheduler( - new TertiaryKeyRotationTracker( - application.getSharedPreferences( - SHARED_PREFS_NAME, Context.MODE_PRIVATE), - MAX_BACKUPS_TILL_ROTATION), - new TertiaryKeyRotationWindowedCount(mFile, mClock), - MAXIMUM_ROTATIONS_PER_WINDOW); - } - - /** Test we don't trigger a rotation straight off */ - @Test - public void isKeyRotationDue_isFalseInitially() { - assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse(); - } - - /** Test we don't prematurely trigger a rotation */ - @Test - public void isKeyRotationDue_isFalseAfterInsufficientBackups() { - simulateBackups(MAX_BACKUPS_TILL_ROTATION - 1); - assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse(); - } - - /** Test we do trigger a backup */ - @Test - public void isKeyRotationDue_isTrueAfterEnoughBackups() { - simulateBackups(MAX_BACKUPS_TILL_ROTATION); - assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isTrue(); - } - - /** Test rotation will occur if the quota allows */ - @Test - public void isKeyRotationDue_isTrueIfRotationQuotaRemainsInWindow() { - simulateBackups(MAX_BACKUPS_TILL_ROTATION); - mScheduler.recordKeyRotation(PACKAGE_2); - assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isTrue(); - } - - /** Test rotation is blocked if the quota has been exhausted */ - @Test - public void isKeyRotationDue_isFalseIfEnoughRotationsHaveHappenedInWindow() { - simulateBackups(MAX_BACKUPS_TILL_ROTATION); - mScheduler.recordKeyRotation(PACKAGE_2); - mScheduler.recordKeyRotation(PACKAGE_2); - assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse(); - } - - /** Test rotation is due after one window has passed */ - @Test - public void isKeyRotationDue_isTrueAfterAWholeWindowHasPassed() { - simulateBackups(MAX_BACKUPS_TILL_ROTATION); - mScheduler.recordKeyRotation(PACKAGE_2); - mScheduler.recordKeyRotation(PACKAGE_2); - setTimeMillis(TimeUnit.HOURS.toMillis(24)); - assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isTrue(); - } - - /** Test the rotation state changes after a rotation */ - @Test - public void isKeyRotationDue_isFalseAfterRotation() { - simulateBackups(MAX_BACKUPS_TILL_ROTATION); - mScheduler.recordKeyRotation(PACKAGE_1); - assertThat(mScheduler.isKeyRotationDue(PACKAGE_1)).isFalse(); - } - - /** Test the rate limiting for a given window */ - @Test - public void isKeyRotationDue_neverAllowsMoreThanInWindow() { - List<String> apps = makeTestApps(MAXIMUM_ROTATIONS_PER_WINDOW * MAX_BACKUPS_TILL_ROTATION); - - // simulate backups of all apps each night - for (int i = 0; i < 300; i++) { - setTimeMillis(i * TimeUnit.HOURS.toMillis(24)); - int rotationsThisNight = 0; - for (String app : apps) { - if (mScheduler.isKeyRotationDue(app)) { - rotationsThisNight++; - mScheduler.recordKeyRotation(app); - } else { - mScheduler.recordBackup(app); - } - } - assertThat(rotationsThisNight).isAtMost(MAXIMUM_ROTATIONS_PER_WINDOW); - } - } - - /** Test that backups are staggered over the window */ - @Test - public void isKeyRotationDue_naturallyStaggersBackupsOverTime() { - List<String> apps = makeTestApps(MAXIMUM_ROTATIONS_PER_WINDOW * MAX_BACKUPS_TILL_ROTATION); - - HashMap<String, ArrayList<Integer>> rotationDays = new HashMap<>(); - for (String app : apps) { - rotationDays.put(app, new ArrayList<>()); - } - - // simulate backups of all apps each night - for (int i = 0; i < 300; i++) { - setTimeMillis(i * TimeUnit.HOURS.toMillis(24)); - for (String app : apps) { - if (mScheduler.isKeyRotationDue(app)) { - rotationDays.get(app).add(i); - mScheduler.recordKeyRotation(app); - } else { - mScheduler.recordBackup(app); - } - } - } - - for (String app : apps) { - List<Integer> days = rotationDays.get(app); - for (int i = 1; i < days.size(); i++) { - assertThat(days.get(i) - days.get(i - 1)).isEqualTo(MAX_BACKUPS_TILL_ROTATION + 1); - } - } - } - - private ArrayList<String> makeTestApps(int n) { - ArrayList<String> apps = new ArrayList<>(); - for (int i = 0; i < n; i++) { - apps.add(String.format(Locale.US, "com.android.app%d", i)); - } - return apps; - } - - private void simulateBackups(int numberOfBackups) { - while (numberOfBackups > 0) { - mScheduler.recordBackup(PACKAGE_1); - numberOfBackups--; - } - } - - private void setTimeMillis(long timeMillis) { - when(mClock.millis()).thenReturn(timeMillis); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java deleted file mode 100644 index 49bb410ceb65..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationTrackerTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2018 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.keys; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -/** Tests for {@link TertiaryKeyRotationTracker}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class TertiaryKeyRotationTrackerTest { - private static final String PACKAGE_1 = "com.package.one"; - private static final int NUMBER_OF_BACKUPS_BEFORE_ROTATION = 31; - - private TertiaryKeyRotationTracker mTertiaryKeyRotationTracker; - - /** Instantiate a {@link TertiaryKeyRotationTracker} for use in tests. */ - @Before - public void setUp() { - mTertiaryKeyRotationTracker = newInstance(); - } - - /** New packages should not be due for key rotation. */ - @Test - public void isKeyRotationDue_forNewPackage_isFalse() { - // Simulate a new package by not calling simulateBackups(). As a result, PACKAGE_1 hasn't - // been seen by mTertiaryKeyRotationTracker before. - boolean keyRotationDue = mTertiaryKeyRotationTracker.isKeyRotationDue(PACKAGE_1); - - assertThat(keyRotationDue).isFalse(); - } - - /** - * Key rotation should not be due after less than {@code NUMBER_OF_BACKUPS_BEFORE_ROTATION} - * backups. - */ - @Test - public void isKeyRotationDue_afterLessThanRotationAmountBackups_isFalse() { - simulateBackups(PACKAGE_1, NUMBER_OF_BACKUPS_BEFORE_ROTATION - 1); - - boolean keyRotationDue = mTertiaryKeyRotationTracker.isKeyRotationDue(PACKAGE_1); - - assertThat(keyRotationDue).isFalse(); - } - - /** Key rotation should be due after {@code NUMBER_OF_BACKUPS_BEFORE_ROTATION} backups. */ - @Test - public void isKeyRotationDue_afterRotationAmountBackups_isTrue() { - simulateBackups(PACKAGE_1, NUMBER_OF_BACKUPS_BEFORE_ROTATION); - - boolean keyRotationDue = mTertiaryKeyRotationTracker.isKeyRotationDue(PACKAGE_1); - - assertThat(keyRotationDue).isTrue(); - } - - /** - * A call to {@link TertiaryKeyRotationTracker#resetCountdown(String)} should make sure no key - * rotation is due. - */ - @Test - public void resetCountdown_makesKeyRotationNotDue() { - simulateBackups(PACKAGE_1, NUMBER_OF_BACKUPS_BEFORE_ROTATION); - - mTertiaryKeyRotationTracker.resetCountdown(PACKAGE_1); - - assertThat(mTertiaryKeyRotationTracker.isKeyRotationDue(PACKAGE_1)).isFalse(); - } - - /** - * New instances of {@link TertiaryKeyRotationTracker} should read state about the number of - * backups from disk. - */ - @Test - public void isKeyRotationDue_forNewInstance_readsStateFromDisk() { - simulateBackups(PACKAGE_1, NUMBER_OF_BACKUPS_BEFORE_ROTATION); - - boolean keyRotationDueForNewInstance = newInstance().isKeyRotationDue(PACKAGE_1); - - assertThat(keyRotationDueForNewInstance).isTrue(); - } - - /** - * A call to {@link TertiaryKeyRotationTracker#markAllForRotation()} should mark all previously - * seen packages for rotation. - */ - @Test - public void markAllForRotation_marksSeenPackagesForKeyRotation() { - simulateBackups(PACKAGE_1, /*numberOfBackups=*/ 1); - - mTertiaryKeyRotationTracker.markAllForRotation(); - - assertThat(mTertiaryKeyRotationTracker.isKeyRotationDue(PACKAGE_1)).isTrue(); - } - - /** - * A call to {@link TertiaryKeyRotationTracker#markAllForRotation()} should not mark any new - * packages for rotation. - */ - @Test - public void markAllForRotation_doesNotMarkUnseenPackages() { - mTertiaryKeyRotationTracker.markAllForRotation(); - - assertThat(mTertiaryKeyRotationTracker.isKeyRotationDue(PACKAGE_1)).isFalse(); - } - - private void simulateBackups(String packageName, int numberOfBackups) { - while (numberOfBackups > 0) { - mTertiaryKeyRotationTracker.recordBackup(packageName); - numberOfBackups--; - } - } - - private static TertiaryKeyRotationTracker newInstance() { - return TertiaryKeyRotationTracker.getInstance(RuntimeEnvironment.application); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCountTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCountTest.java deleted file mode 100644 index bd309779f303..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyRotationWindowedCountTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * 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.keys; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.when; - -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 org.robolectric.RobolectricTestRunner; - -import java.io.File; -import java.io.IOException; -import java.time.Clock; -import java.util.concurrent.TimeUnit; - -/** Tests for {@link TertiaryKeyRotationWindowedCount}. */ -@RunWith(RobolectricTestRunner.class) -public class TertiaryKeyRotationWindowedCountTest { - private static final int TIMESTAMP_SIZE_IN_BYTES = 8; - - @Rule public final TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - - @Mock private Clock mClock; - - private File mFile; - private TertiaryKeyRotationWindowedCount mWindowedcount; - - /** Setup the windowed counter for testing */ - @Before - public void setUp() throws IOException { - MockitoAnnotations.initMocks(this); - mFile = mTemporaryFolder.newFile(); - mWindowedcount = new TertiaryKeyRotationWindowedCount(mFile, mClock); - } - - /** Test handling bad files */ - @Test - public void constructor_doesNotFailForBadFile() throws IOException { - new TertiaryKeyRotationWindowedCount(mTemporaryFolder.newFolder(), mClock); - } - - /** Test the count is 0 to start */ - @Test - public void getCount_isZeroInitially() { - assertThat(mWindowedcount.getCount()).isEqualTo(0); - } - - /** Test the count is correct for a time window */ - @Test - public void getCount_includesResultsInLastTwentyFourHours() { - setTimeMillis(0); - mWindowedcount.record(); - setTimeMillis(TimeUnit.HOURS.toMillis(4)); - mWindowedcount.record(); - setTimeMillis(TimeUnit.HOURS.toMillis(23)); - mWindowedcount.record(); - mWindowedcount.record(); - assertThat(mWindowedcount.getCount()).isEqualTo(4); - } - - /** Test old results are ignored */ - @Test - public void getCount_ignoresResultsOlderThanTwentyFourHours() { - setTimeMillis(0); - mWindowedcount.record(); - setTimeMillis(TimeUnit.HOURS.toMillis(24)); - assertThat(mWindowedcount.getCount()).isEqualTo(0); - } - - /** Test future events are removed if the clock moves backways (e.g. DST, TZ change) */ - @Test - public void getCount_removesFutureEventsIfClockHasChanged() { - setTimeMillis(1000); - mWindowedcount.record(); - setTimeMillis(0); - assertThat(mWindowedcount.getCount()).isEqualTo(0); - } - - /** Check recording doesn't fail for a bad file */ - @Test - public void record_doesNotFailForBadFile() throws Exception { - new TertiaryKeyRotationWindowedCount(mTemporaryFolder.newFolder(), mClock).record(); - } - - /** Checks the state is persisted */ - @Test - public void record_persistsStateToDisk() { - setTimeMillis(0); - mWindowedcount.record(); - assertThat(new TertiaryKeyRotationWindowedCount(mFile, mClock).getCount()).isEqualTo(1); - } - - /** Test the file doesn't contain unnecessary data */ - @Test - public void record_compactsFileToLast24Hours() { - setTimeMillis(0); - mWindowedcount.record(); - assertThat(mFile.length()).isEqualTo(TIMESTAMP_SIZE_IN_BYTES); - setTimeMillis(1); - mWindowedcount.record(); - assertThat(mFile.length()).isEqualTo(2 * TIMESTAMP_SIZE_IN_BYTES); - setTimeMillis(TimeUnit.HOURS.toMillis(24)); - mWindowedcount.record(); - assertThat(mFile.length()).isEqualTo(2 * TIMESTAMP_SIZE_IN_BYTES); - } - - private void setTimeMillis(long timeMillis) { - when(mClock.millis()).thenReturn(timeMillis); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyStoreTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyStoreTest.java deleted file mode 100644 index ccc5f32dad36..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/keys/TertiaryKeyStoreTest.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * 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.keys; - -import static com.android.server.backup.testing.CryptoTestUtils.generateAesKey; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.assertTrue; - -import android.content.Context; - -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.security.InvalidKeyException; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; - -import javax.crypto.SecretKey; - -/** Tests for the tertiary key store */ -@RunWith(RobolectricTestRunner.class) -public class TertiaryKeyStoreTest { - - private static final String SECONDARY_KEY_ALIAS = "Robbo/Ranx"; - - private Context mApplication; - private TertiaryKeyStore mTertiaryKeyStore; - private SecretKey mSecretKey; - - /** Initialise the keystore for testing */ - @Before - public void setUp() throws Exception { - mApplication = RuntimeEnvironment.application; - mSecretKey = generateAesKey(); - mTertiaryKeyStore = - TertiaryKeyStore.newInstance( - mApplication, - new RecoverableKeyStoreSecondaryKey(SECONDARY_KEY_ALIAS, mSecretKey)); - } - - /** Test a reound trip for a key */ - @Test - public void load_loadsAKeyThatWasSaved() throws Exception { - String packageName = "com.android.example"; - SecretKey packageKey = generateAesKey(); - mTertiaryKeyStore.save(packageName, packageKey); - - Optional<SecretKey> maybeLoadedKey = mTertiaryKeyStore.load(packageName); - - assertTrue(maybeLoadedKey.isPresent()); - assertEquals(packageKey, maybeLoadedKey.get()); - } - - /** Test isolation between packages */ - @Test - public void load_doesNotLoadAKeyForAnotherSecondary() throws Exception { - String packageName = "com.android.example"; - SecretKey packageKey = generateAesKey(); - mTertiaryKeyStore.save(packageName, packageKey); - TertiaryKeyStore managerWithOtherSecondaryKey = - TertiaryKeyStore.newInstance( - mApplication, - new RecoverableKeyStoreSecondaryKey( - "myNewSecondaryKeyAlias", generateAesKey())); - - assertFalse(managerWithOtherSecondaryKey.load(packageName).isPresent()); - } - - /** Test non-existent key handling */ - @Test - public void load_returnsAbsentForANonExistentKey() throws Exception { - assertFalse(mTertiaryKeyStore.load("mystery.package").isPresent()); - } - - /** Test handling incorrect keys */ - @Test - public void load_throwsIfHasWrongBackupKey() throws Exception { - String packageName = "com.android.example"; - SecretKey packageKey = generateAesKey(); - mTertiaryKeyStore.save(packageName, packageKey); - TertiaryKeyStore managerWithBadKey = - TertiaryKeyStore.newInstance( - mApplication, - new RecoverableKeyStoreSecondaryKey(SECONDARY_KEY_ALIAS, generateAesKey())); - - assertThrows(InvalidKeyException.class, () -> managerWithBadKey.load(packageName)); - } - - /** Test handling of empty app name */ - @Test - public void load_throwsForEmptyApplicationName() throws Exception { - assertThrows(IllegalArgumentException.class, () -> mTertiaryKeyStore.load("")); - } - - /** Test handling of an invalid app name */ - @Test - public void load_throwsForBadApplicationName() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> mTertiaryKeyStore.load("com/android/example")); - } - - /** Test key replacement */ - @Test - public void save_overwritesPreviousKey() throws Exception { - String packageName = "com.android.example"; - SecretKey oldKey = generateAesKey(); - mTertiaryKeyStore.save(packageName, oldKey); - SecretKey newKey = generateAesKey(); - - mTertiaryKeyStore.save(packageName, newKey); - - Optional<SecretKey> maybeLoadedKey = mTertiaryKeyStore.load(packageName); - assertTrue(maybeLoadedKey.isPresent()); - SecretKey loadedKey = maybeLoadedKey.get(); - assertThat(loadedKey).isNotEqualTo(oldKey); - assertThat(loadedKey).isEqualTo(newKey); - } - - /** Test saving with an empty application name fails */ - @Test - public void save_throwsForEmptyApplicationName() throws Exception { - assertThrows( - IllegalArgumentException.class, () -> mTertiaryKeyStore.save("", generateAesKey())); - } - - /** Test saving an invalid application name fails */ - @Test - public void save_throwsForBadApplicationName() throws Exception { - assertThrows( - IllegalArgumentException.class, - () -> mTertiaryKeyStore.save("com/android/example", generateAesKey())); - } - - /** Test handling an empty database */ - @Test - public void getAll_returnsEmptyMapForEmptyDb() throws Exception { - assertThat(mTertiaryKeyStore.getAll()).isEmpty(); - } - - /** Test loading all available keys works as expected */ - @Test - public void getAll_returnsAllKeysSaved() throws Exception { - String package1 = "com.android.example"; - SecretKey key1 = generateAesKey(); - String package2 = "com.anndroid.example1"; - SecretKey key2 = generateAesKey(); - String package3 = "com.android.example2"; - SecretKey key3 = generateAesKey(); - mTertiaryKeyStore.save(package1, key1); - mTertiaryKeyStore.save(package2, key2); - mTertiaryKeyStore.save(package3, key3); - - Map<String, SecretKey> keys = mTertiaryKeyStore.getAll(); - - assertThat(keys).containsExactly(package1, key1, package2, key2, package3, key3); - } - - /** Test cross-secondary isolation */ - @Test - public void getAll_doesNotReturnKeysForOtherSecondary() throws Exception { - String packageName = "com.android.example"; - TertiaryKeyStore managerWithOtherSecondaryKey = - TertiaryKeyStore.newInstance( - mApplication, - new RecoverableKeyStoreSecondaryKey( - "myNewSecondaryKeyAlias", generateAesKey())); - managerWithOtherSecondaryKey.save(packageName, generateAesKey()); - - assertThat(mTertiaryKeyStore.getAll()).isEmpty(); - } - - /** Test mass put into the keystore */ - @Test - public void putAll_putsAllWrappedKeysInTheStore() throws Exception { - String packageName = "com.android.example"; - SecretKey key = generateAesKey(); - WrappedKeyProto.WrappedKey wrappedKey = KeyWrapUtils.wrap(mSecretKey, key); - - Map<String, WrappedKeyProto.WrappedKey> testElements = new HashMap<>(); - testElements.put(packageName, wrappedKey); - mTertiaryKeyStore.putAll(testElements); - - assertThat(mTertiaryKeyStore.getAll()).containsKey(packageName); - assertThat(mTertiaryKeyStore.getAll().get(packageName).getEncoded()) - .isEqualTo(key.getEncoded()); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java deleted file mode 100644 index 215e1cbc725e..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutputTest.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * 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.kv; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; - -import android.os.Debug; -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.ChunkHasher; -import com.android.server.backup.encryption.protos.nano.KeyValuePairProto; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; - -import java.security.MessageDigest; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.stream.Stream; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class DecryptedChunkKvOutputTest { - private static final String TEST_KEY_1 = "key_1"; - private static final String TEST_KEY_2 = "key_2"; - private static final byte[] TEST_VALUE_1 = {1, 2, 3}; - private static final byte[] TEST_VALUE_2 = {10, 11, 12, 13}; - private static final byte[] TEST_PAIR_1 = toByteArray(createPair(TEST_KEY_1, TEST_VALUE_1)); - private static final byte[] TEST_PAIR_2 = toByteArray(createPair(TEST_KEY_2, TEST_VALUE_2)); - private static final int TEST_BUFFER_SIZE = Math.max(TEST_PAIR_1.length, TEST_PAIR_2.length); - - @Mock private ChunkHasher mChunkHasher; - private DecryptedChunkKvOutput mOutput; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - when(mChunkHasher.computeHash(any())) - .thenAnswer(invocation -> fakeHash(invocation.getArgument(0))); - mOutput = new DecryptedChunkKvOutput(mChunkHasher); - } - - @Test - public void open_returnsInstance() throws Exception { - assertThat(mOutput.open()).isEqualTo(mOutput); - } - - @Test - public void processChunk_alreadyClosed_throws() throws Exception { - mOutput.open(); - mOutput.close(); - - assertThrows( - IllegalStateException.class, - () -> mOutput.processChunk(TEST_PAIR_1, TEST_PAIR_1.length)); - } - - @Test - public void getDigest_beforeClose_throws() throws Exception { - // TODO: b/141356823 We should add a test which calls .open() here - assertThrows(IllegalStateException.class, () -> mOutput.getDigest()); - } - - @Test - public void getDigest_returnsDigestOfSortedHashes() throws Exception { - mOutput.open(); - Debug.waitForDebugger(); - mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length); - mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length); - mOutput.close(); - - byte[] actualDigest = mOutput.getDigest(); - - MessageDigest digest = MessageDigest.getInstance(DecryptedChunkKvOutput.DIGEST_ALGORITHM); - Stream.of(TEST_PAIR_1, TEST_PAIR_2) - .map(DecryptedChunkKvOutputTest::fakeHash) - .sorted(Comparator.naturalOrder()) - .forEachOrdered(hash -> digest.update(hash.getHash())); - assertThat(actualDigest).isEqualTo(digest.digest()); - } - - @Test - public void getPairs_beforeClose_throws() throws Exception { - // TODO: b/141356823 We should add a test which calls .open() here - assertThrows(IllegalStateException.class, () -> mOutput.getPairs()); - } - - @Test - public void getPairs_returnsPairsSortedByKey() throws Exception { - mOutput.open(); - // Write out of order to check that it sorts the chunks. - mOutput.processChunk(Arrays.copyOf(TEST_PAIR_2, TEST_BUFFER_SIZE), TEST_PAIR_2.length); - mOutput.processChunk(Arrays.copyOf(TEST_PAIR_1, TEST_BUFFER_SIZE), TEST_PAIR_1.length); - mOutput.close(); - - List<KeyValuePairProto.KeyValuePair> pairs = mOutput.getPairs(); - - assertThat( - isInOrder( - pairs, - Comparator.comparing( - (KeyValuePairProto.KeyValuePair pair) -> pair.key))) - .isTrue(); - assertThat(pairs).hasSize(2); - assertThat(pairs.get(0).key).isEqualTo(TEST_KEY_1); - assertThat(pairs.get(0).value).isEqualTo(TEST_VALUE_1); - assertThat(pairs.get(1).key).isEqualTo(TEST_KEY_2); - assertThat(pairs.get(1).value).isEqualTo(TEST_VALUE_2); - } - - private static KeyValuePairProto.KeyValuePair createPair(String key, byte[] value) { - KeyValuePairProto.KeyValuePair pair = new KeyValuePairProto.KeyValuePair(); - pair.key = key; - pair.value = value; - return pair; - } - - private boolean isInOrder( - List<KeyValuePairProto.KeyValuePair> list, - Comparator<KeyValuePairProto.KeyValuePair> comparator) { - if (list.size() < 2) { - return true; - } - - List<KeyValuePairProto.KeyValuePair> sortedList = new ArrayList<>(list); - Collections.sort(sortedList, comparator); - return list.equals(sortedList); - } - - private static byte[] toByteArray(KeyValuePairProto.KeyValuePair nano) { - return KeyValuePairProto.KeyValuePair.toByteArray(nano); - } - - private static ChunkHash fakeHash(byte[] data) { - return new ChunkHash(Arrays.copyOf(data, ChunkHash.HASH_LENGTH_BYTES)); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java deleted file mode 100644 index acc662860528..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/kv/KeyValueListingBuilderTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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.kv; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.protos.nano.KeyValueListingProto; - -import com.google.common.collect.ImmutableMap; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.util.Arrays; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class KeyValueListingBuilderTest { - private static final String TEST_KEY_1 = "test_key_1"; - private static final String TEST_KEY_2 = "test_key_2"; - private static final ChunkHash TEST_HASH_1 = - new ChunkHash(Arrays.copyOf(new byte[] {1, 2}, ChunkHash.HASH_LENGTH_BYTES)); - private static final ChunkHash TEST_HASH_2 = - new ChunkHash(Arrays.copyOf(new byte[] {5, 6}, ChunkHash.HASH_LENGTH_BYTES)); - - private KeyValueListingBuilder mBuilder; - - @Before - public void setUp() { - mBuilder = new KeyValueListingBuilder(); - } - - @Test - public void addPair_nullKey_throws() { - assertThrows(NullPointerException.class, () -> mBuilder.addPair(null, TEST_HASH_1)); - } - - @Test - public void addPair_emptyKey_throws() { - assertThrows(IllegalArgumentException.class, () -> mBuilder.addPair("", TEST_HASH_1)); - } - - @Test - public void addPair_nullHash_throws() { - assertThrows(NullPointerException.class, () -> mBuilder.addPair(TEST_KEY_1, null)); - } - - @Test - public void build_noPairs_buildsEmptyListing() { - KeyValueListingProto.KeyValueListing listing = mBuilder.build(); - - assertThat(listing.entries).isEmpty(); - } - - @Test - public void build_returnsCorrectListing() { - mBuilder.addPair(TEST_KEY_1, TEST_HASH_1); - - KeyValueListingProto.KeyValueListing listing = mBuilder.build(); - - assertThat(listing.entries.length).isEqualTo(1); - assertThat(listing.entries[0].key).isEqualTo(TEST_KEY_1); - assertThat(listing.entries[0].hash).isEqualTo(TEST_HASH_1.getHash()); - } - - @Test - public void addAll_addsAllPairsInMap() { - ImmutableMap<String, ChunkHash> pairs = - new ImmutableMap.Builder<String, ChunkHash>() - .put(TEST_KEY_1, TEST_HASH_1) - .put(TEST_KEY_2, TEST_HASH_2) - .build(); - - mBuilder.addAll(pairs); - KeyValueListingProto.KeyValueListing listing = mBuilder.build(); - - assertThat(listing.entries.length).isEqualTo(2); - assertThat(listing.entries[0].key).isEqualTo(TEST_KEY_1); - assertThat(listing.entries[0].hash).isEqualTo(TEST_HASH_1.getHash()); - assertThat(listing.entries[1].key).isEqualTo(TEST_KEY_2); - assertThat(listing.entries[1].hash).isEqualTo(TEST_HASH_2.getHash()); - } - - @Test - public void emptyListing_returnsListingWithoutAnyPairs() { - KeyValueListingProto.KeyValueListing emptyListing = KeyValueListingBuilder.emptyListing(); - assertThat(emptyListing.entries).isEmpty(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java deleted file mode 100644 index 87f21bfa59c2..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/BackupEncryptionDbTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2018 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.storage; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -/** Tests for {@link BackupEncryptionDb}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class BackupEncryptionDbTest { - private BackupEncryptionDb mBackupEncryptionDb; - - /** Creates an empty {@link BackupEncryptionDb} */ - @Before - public void setUp() { - mBackupEncryptionDb = BackupEncryptionDb.newInstance(RuntimeEnvironment.application); - } - - /** - * Tests that the tertiary keys table gets cleared when calling {@link - * BackupEncryptionDb#clear()}. - */ - @Test - public void clear_withNonEmptyTertiaryKeysTable_clearsTertiaryKeysTable() throws Exception { - String secondaryKeyAlias = "secondaryKeyAlias"; - TertiaryKeysTable tertiaryKeysTable = mBackupEncryptionDb.getTertiaryKeysTable(); - tertiaryKeysTable.addKey(new TertiaryKey(secondaryKeyAlias, "packageName", new byte[0])); - - mBackupEncryptionDb.clear(); - - assertThat(tertiaryKeysTable.getAllKeys(secondaryKeyAlias)).isEmpty(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java deleted file mode 100644 index 319ec89f445e..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/storage/TertiaryKeysTableTest.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2018 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.storage; - -import static com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.testing.CryptoTestUtils; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; - -import java.util.Map; -import java.util.Optional; - -/** Tests for {@link TertiaryKeysTable}. */ -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class TertiaryKeysTableTest { - private static final int KEY_SIZE_BYTES = 32; - private static final String SECONDARY_ALIAS = "phoebe"; - private static final String PACKAGE_NAME = "generic.package.name"; - - private TertiaryKeysTable mTertiaryKeysTable; - - /** Creates an empty {@link BackupEncryptionDb}. */ - @Before - public void setUp() { - mTertiaryKeysTable = - BackupEncryptionDb.newInstance(RuntimeEnvironment.application) - .getTertiaryKeysTable(); - } - - /** Tests that new {@link TertiaryKey}s get successfully added to the database. */ - @Test - public void addKey_onEmptyDatabase_putsKeyInDb() throws Exception { - byte[] key = generateRandomKey(); - TertiaryKey keyToInsert = new TertiaryKey(SECONDARY_ALIAS, PACKAGE_NAME, key); - - long result = mTertiaryKeysTable.addKey(keyToInsert); - - assertThat(result).isNotEqualTo(-1); - Optional<TertiaryKey> maybeKeyInDb = - mTertiaryKeysTable.getKey(SECONDARY_ALIAS, PACKAGE_NAME); - assertThat(maybeKeyInDb.isPresent()).isTrue(); - TertiaryKey keyInDb = maybeKeyInDb.get(); - assertTertiaryKeysEqual(keyInDb, keyToInsert); - } - - /** Tests that keys replace older keys with the same secondary alias and package name. */ - @Test - public void addKey_havingSameSecondaryAliasAndPackageName_replacesOldKey() throws Exception { - mTertiaryKeysTable.addKey( - new TertiaryKey(SECONDARY_ALIAS, PACKAGE_NAME, generateRandomKey())); - byte[] newKey = generateRandomKey(); - - long result = - mTertiaryKeysTable.addKey(new TertiaryKey(SECONDARY_ALIAS, PACKAGE_NAME, newKey)); - - assertThat(result).isNotEqualTo(-1); - TertiaryKey keyInDb = mTertiaryKeysTable.getKey(SECONDARY_ALIAS, PACKAGE_NAME).get(); - assertThat(keyInDb.getWrappedKeyBytes()).isEqualTo(newKey); - } - - /** - * Tests that keys do not replace older keys with the same package name but a different alias. - */ - @Test - public void addKey_havingSamePackageNameButDifferentAlias_doesNotReplaceOldKey() - throws Exception { - String alias2 = "karl"; - TertiaryKey key1 = generateTertiaryKey(SECONDARY_ALIAS, PACKAGE_NAME); - TertiaryKey key2 = generateTertiaryKey(alias2, PACKAGE_NAME); - - long primaryKey1 = mTertiaryKeysTable.addKey(key1); - long primaryKey2 = mTertiaryKeysTable.addKey(key2); - - assertThat(primaryKey1).isNotEqualTo(primaryKey2); - assertThat(mTertiaryKeysTable.getKey(SECONDARY_ALIAS, PACKAGE_NAME).isPresent()).isTrue(); - assertTertiaryKeysEqual( - mTertiaryKeysTable.getKey(SECONDARY_ALIAS, PACKAGE_NAME).get(), key1); - assertThat(mTertiaryKeysTable.getKey(alias2, PACKAGE_NAME).isPresent()).isTrue(); - assertTertiaryKeysEqual(mTertiaryKeysTable.getKey(alias2, PACKAGE_NAME).get(), key2); - } - - /** - * Tests that {@link TertiaryKeysTable#getKey(String, String)} returns an empty {@link Optional} - * for a missing key. - */ - @Test - public void getKey_forMissingKey_returnsEmptyOptional() throws Exception { - Optional<TertiaryKey> key = mTertiaryKeysTable.getKey(SECONDARY_ALIAS, PACKAGE_NAME); - - assertThat(key.isPresent()).isFalse(); - } - - /** - * Tests that {@link TertiaryKeysTable#getAllKeys(String)} returns an empty map when no keys - * with the secondary alias exist. - */ - @Test - public void getAllKeys_withNoKeysForAlias_returnsEmptyMap() throws Exception { - assertThat(mTertiaryKeysTable.getAllKeys(SECONDARY_ALIAS)).isEmpty(); - } - - /** - * Tests that {@link TertiaryKeysTable#getAllKeys(String)} returns all keys corresponding to the - * provided secondary alias. - */ - @Test - public void getAllKeys_withMatchingKeys_returnsAllKeysWrappedWithSecondary() throws Exception { - TertiaryKey key1 = generateTertiaryKey(SECONDARY_ALIAS, PACKAGE_NAME); - mTertiaryKeysTable.addKey(key1); - String package2 = "generic.package.two"; - TertiaryKey key2 = generateTertiaryKey(SECONDARY_ALIAS, package2); - mTertiaryKeysTable.addKey(key2); - String package3 = "generic.package.three"; - TertiaryKey key3 = generateTertiaryKey(SECONDARY_ALIAS, package3); - mTertiaryKeysTable.addKey(key3); - - Map<String, TertiaryKey> keysByPackageName = mTertiaryKeysTable.getAllKeys(SECONDARY_ALIAS); - - assertThat(keysByPackageName).hasSize(3); - assertThat(keysByPackageName).containsKey(PACKAGE_NAME); - assertTertiaryKeysEqual(keysByPackageName.get(PACKAGE_NAME), key1); - assertThat(keysByPackageName).containsKey(package2); - assertTertiaryKeysEqual(keysByPackageName.get(package2), key2); - assertThat(keysByPackageName).containsKey(package3); - assertTertiaryKeysEqual(keysByPackageName.get(package3), key3); - } - - /** - * Tests that {@link TertiaryKeysTable#getAllKeys(String)} does not return any keys wrapped with - * another alias. - */ - @Test - public void getAllKeys_withMatchingKeys_doesNotReturnKeysWrappedWithOtherAlias() - throws Exception { - mTertiaryKeysTable.addKey(generateTertiaryKey(SECONDARY_ALIAS, PACKAGE_NAME)); - mTertiaryKeysTable.addKey(generateTertiaryKey("somekey", "generic.package.two")); - - Map<String, TertiaryKey> keysByPackageName = mTertiaryKeysTable.getAllKeys(SECONDARY_ALIAS); - - assertThat(keysByPackageName).hasSize(1); - assertThat(keysByPackageName).containsKey(PACKAGE_NAME); - } - - private void assertTertiaryKeysEqual(TertiaryKey a, TertiaryKey b) { - assertThat(a.getSecondaryKeyAlias()).isEqualTo(b.getSecondaryKeyAlias()); - assertThat(a.getPackageName()).isEqualTo(b.getPackageName()); - assertThat(a.getWrappedKeyBytes()).isEqualTo(b.getWrappedKeyBytes()); - } - - private TertiaryKey generateTertiaryKey(String alias, String packageName) { - return new TertiaryKey(alias, packageName, generateRandomKey()); - } - - private byte[] generateRandomKey() { - return CryptoTestUtils.generateRandomBytes(KEY_SIZE_BYTES); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTaskTest.java deleted file mode 100644 index 07a6fd2d5b60..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupFileDecryptorTaskTest.java +++ /dev/null @@ -1,583 +0,0 @@ -/* - * 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 com.android.server.backup.testing.CryptoTestUtils.generateAesKey; -import static com.android.server.backup.testing.CryptoTestUtils.newChunkOrdering; -import static com.android.server.backup.testing.CryptoTestUtils.newChunksMetadata; -import static com.android.server.backup.testing.CryptoTestUtils.newPair; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.expectThrows; - -import android.annotation.Nullable; -import android.app.backup.BackupDataInput; -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.ChunkHasher; -import com.android.server.backup.encryption.chunking.DecryptedChunkFileOutput; -import com.android.server.backup.encryption.chunking.EncryptedChunk; -import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer; -import com.android.server.backup.encryption.kv.DecryptedChunkKvOutput; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkOrdering; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunksMetadata; -import com.android.server.backup.encryption.protos.nano.KeyValuePairProto.KeyValuePair; -import com.android.server.backup.encryption.tasks.BackupEncrypter.Result; -import com.android.server.backup.testing.CryptoTestUtils; -import com.android.server.testing.shadows.ShadowBackupDataInput; - -import com.google.common.collect.ImmutableMap; -import com.google.protobuf.nano.MessageNano; - -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.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileDescriptor; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.RandomAccessFile; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; - -import javax.crypto.AEADBadTagException; -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.GCMParameterSpec; - -@Config(shadows = {ShadowBackupDataInput.class}) -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class BackupFileDecryptorTaskTest { - private static final String READ_WRITE_MODE = "rw"; - private static final int BYTES_PER_KILOBYTE = 1024; - private static final int MIN_CHUNK_SIZE_BYTES = 2 * BYTES_PER_KILOBYTE; - private static final int AVERAGE_CHUNK_SIZE_BYTES = 4 * BYTES_PER_KILOBYTE; - private static final int MAX_CHUNK_SIZE_BYTES = 64 * BYTES_PER_KILOBYTE; - private static final int BACKUP_DATA_SIZE_BYTES = 60 * BYTES_PER_KILOBYTE; - private static final int GCM_NONCE_LENGTH_BYTES = 12; - private static final int GCM_TAG_LENGTH_BYTES = 16; - private static final int BITS_PER_BYTE = 8; - private static final int CHECKSUM_LENGTH_BYTES = 256 / BITS_PER_BYTE; - @Nullable private static final FileDescriptor NULL_FILE_DESCRIPTOR = null; - - private static final Set<KeyValuePair> TEST_KV_DATA = new HashSet<>(); - - static { - TEST_KV_DATA.add(newPair("key1", "value1")); - TEST_KV_DATA.add(newPair("key2", "value2")); - } - - @Rule public final TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - - private SecretKey mTertiaryKey; - private SecretKey mChunkEncryptionKey; - private File mInputFile; - private File mOutputFile; - private DecryptedChunkOutput mFileOutput; - private DecryptedChunkKvOutput mKvOutput; - private Random mRandom; - private BackupFileDecryptorTask mTask; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mRandom = new Random(); - mTertiaryKey = generateAesKey(); - // In good situations it's always the same. We allow changing it for testing when somehow it - // has become mismatched that we throw an error. - mChunkEncryptionKey = mTertiaryKey; - mInputFile = mTemporaryFolder.newFile(); - mOutputFile = mTemporaryFolder.newFile(); - mFileOutput = new DecryptedChunkFileOutput(mOutputFile); - mKvOutput = new DecryptedChunkKvOutput(new ChunkHasher(mTertiaryKey)); - mTask = new BackupFileDecryptorTask(mTertiaryKey); - } - - @Test - public void decryptFile_throwsForNonExistentInput() throws Exception { - assertThrows( - FileNotFoundException.class, - () -> - mTask.decryptFile( - new File(mTemporaryFolder.newFolder(), "nonexistent"), - mFileOutput)); - } - - @Test - public void decryptFile_throwsForDirectoryInputFile() throws Exception { - assertThrows( - FileNotFoundException.class, - () -> mTask.decryptFile(mTemporaryFolder.newFolder(), mFileOutput)); - } - - @Test - public void decryptFile_withExplicitStarts_decryptsEncryptedData() throws Exception { - byte[] backupData = randomData(BACKUP_DATA_SIZE_BYTES); - createEncryptedFileUsingExplicitStarts(backupData); - - mTask.decryptFile(mInputFile, mFileOutput); - - assertThat(Files.readAllBytes(Paths.get(mOutputFile.toURI()))).isEqualTo(backupData); - } - - @Test - public void decryptFile_withInlineLengths_decryptsEncryptedData() throws Exception { - createEncryptedFileUsingInlineLengths( - TEST_KV_DATA, chunkOrdering -> chunkOrdering, chunksMetadata -> chunksMetadata); - mTask.decryptFile(mInputFile, mKvOutput); - assertThat(asMap(mKvOutput.getPairs())).containsExactlyEntriesIn(asMap(TEST_KV_DATA)); - } - - @Test - public void decryptFile_withNoChunkOrderingType_decryptsUsingExplicitStarts() throws Exception { - byte[] backupData = randomData(BACKUP_DATA_SIZE_BYTES); - createEncryptedFileUsingExplicitStarts( - backupData, - chunkOrdering -> chunkOrdering, - chunksMetadata -> { - ChunksMetadata metadata = CryptoTestUtils.clone(chunksMetadata); - metadata.chunkOrderingType = - ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED; - return metadata; - }); - - mTask.decryptFile(mInputFile, mFileOutput); - - assertThat(Files.readAllBytes(Paths.get(mOutputFile.toURI()))).isEqualTo(backupData); - } - - @Test - public void decryptFile_withInlineLengths_throwsForZeroLengths() throws Exception { - createEncryptedFileUsingInlineLengths( - TEST_KV_DATA, chunkOrdering -> chunkOrdering, chunksMetadata -> chunksMetadata); - - // Set the length of the first chunk to zero. - RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE); - raf.seek(0); - raf.writeInt(0); - - assertThrows( - MalformedEncryptedFileException.class, - () -> mTask.decryptFile(mInputFile, mKvOutput)); - } - - @Test - public void decryptFile_withInlineLengths_throwsForLongLengths() throws Exception { - createEncryptedFileUsingInlineLengths( - TEST_KV_DATA, chunkOrdering -> chunkOrdering, chunksMetadata -> chunksMetadata); - - // Set the length of the first chunk to zero. - RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE); - raf.seek(0); - raf.writeInt((int) mInputFile.length()); - - assertThrows( - MalformedEncryptedFileException.class, - () -> mTask.decryptFile(mInputFile, mKvOutput)); - } - - @Test - public void decryptFile_throwsForBadKey() throws Exception { - createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES)); - - assertThrows( - AEADBadTagException.class, - () -> - new BackupFileDecryptorTask(generateAesKey()) - .decryptFile(mInputFile, mFileOutput)); - } - - @Test - public void decryptFile_withExplicitStarts_throwsForMangledOrdering() throws Exception { - createEncryptedFileUsingExplicitStarts( - randomData(BACKUP_DATA_SIZE_BYTES), - chunkOrdering -> { - ChunkOrdering ordering = CryptoTestUtils.clone(chunkOrdering); - Arrays.sort(ordering.starts); - return ordering; - }); - - assertThrows( - MessageDigestMismatchException.class, - () -> mTask.decryptFile(mInputFile, mFileOutput)); - } - - @Test - public void decryptFile_withExplicitStarts_noChunks_returnsNoData() throws Exception { - byte[] backupData = randomData(/*length=*/ 0); - createEncryptedFileUsingExplicitStarts( - backupData, - chunkOrdering -> { - ChunkOrdering ordering = CryptoTestUtils.clone(chunkOrdering); - ordering.starts = new int[0]; - return ordering; - }); - - mTask.decryptFile(mInputFile, mFileOutput); - - assertThat(Files.readAllBytes(Paths.get(mOutputFile.toURI()))).isEqualTo(backupData); - } - - @Test - public void decryptFile_throwsForMismatchedChecksum() throws Exception { - createEncryptedFileUsingExplicitStarts( - randomData(BACKUP_DATA_SIZE_BYTES), - chunkOrdering -> { - ChunkOrdering ordering = CryptoTestUtils.clone(chunkOrdering); - ordering.checksum = - Arrays.copyOf(randomData(CHECKSUM_LENGTH_BYTES), CHECKSUM_LENGTH_BYTES); - return ordering; - }); - - assertThrows( - MessageDigestMismatchException.class, - () -> mTask.decryptFile(mInputFile, mFileOutput)); - } - - @Test - public void decryptFile_throwsForBadChunksMetadataOffset() throws Exception { - createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES)); - - // Replace the metadata with all 1s. - RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE); - raf.seek(raf.length() - Long.BYTES); - int metadataOffset = (int) raf.readLong(); - int metadataLength = (int) raf.length() - metadataOffset - Long.BYTES; - - byte[] allOnes = new byte[metadataLength]; - Arrays.fill(allOnes, (byte) 1); - - raf.seek(metadataOffset); - raf.write(allOnes, /*off=*/ 0, metadataLength); - - MalformedEncryptedFileException thrown = - expectThrows( - MalformedEncryptedFileException.class, - () -> mTask.decryptFile(mInputFile, mFileOutput)); - assertThat(thrown) - .hasMessageThat() - .isEqualTo( - "Could not read chunks metadata at position " - + metadataOffset - + " of file of " - + raf.length() - + " bytes"); - } - - @Test - public void decryptFile_throwsForChunksMetadataOffsetBeyondEndOfFile() throws Exception { - createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES)); - - RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE); - raf.seek(raf.length() - Long.BYTES); - raf.writeLong(raf.length()); - - MalformedEncryptedFileException thrown = - expectThrows( - MalformedEncryptedFileException.class, - () -> mTask.decryptFile(mInputFile, mFileOutput)); - assertThat(thrown) - .hasMessageThat() - .isEqualTo( - raf.length() - + " is not valid position for chunks metadata in file of " - + raf.length() - + " bytes"); - } - - @Test - public void decryptFile_throwsForChunksMetadataOffsetBeforeBeginningOfFile() throws Exception { - createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES)); - - RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE); - raf.seek(raf.length() - Long.BYTES); - raf.writeLong(-1); - - MalformedEncryptedFileException thrown = - expectThrows( - MalformedEncryptedFileException.class, - () -> mTask.decryptFile(mInputFile, mFileOutput)); - assertThat(thrown) - .hasMessageThat() - .isEqualTo( - "-1 is not valid position for chunks metadata in file of " - + raf.length() - + " bytes"); - } - - @Test - public void decryptFile_throwsForMangledChunks() throws Exception { - createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES)); - - // Mess up some bits in a random byte - RandomAccessFile raf = new RandomAccessFile(mInputFile, READ_WRITE_MODE); - raf.seek(50); - byte fiftiethByte = raf.readByte(); - raf.seek(50); - raf.write(~fiftiethByte); - - assertThrows(AEADBadTagException.class, () -> mTask.decryptFile(mInputFile, mFileOutput)); - } - - @Test - public void decryptFile_throwsForBadChunkEncryptionKey() throws Exception { - mChunkEncryptionKey = generateAesKey(); - - createEncryptedFileUsingExplicitStarts(randomData(BACKUP_DATA_SIZE_BYTES)); - - assertThrows(AEADBadTagException.class, () -> mTask.decryptFile(mInputFile, mFileOutput)); - } - - @Test - public void decryptFile_throwsForUnsupportedCipherType() throws Exception { - createEncryptedFileUsingExplicitStarts( - randomData(BACKUP_DATA_SIZE_BYTES), - chunkOrdering -> chunkOrdering, - chunksMetadata -> { - ChunksMetadata metadata = CryptoTestUtils.clone(chunksMetadata); - metadata.cipherType = ChunksMetadataProto.UNKNOWN_CIPHER_TYPE; - return metadata; - }); - - assertThrows( - UnsupportedEncryptedFileException.class, - () -> mTask.decryptFile(mInputFile, mFileOutput)); - } - - @Test - public void decryptFile_throwsForUnsupportedMessageDigestType() throws Exception { - createEncryptedFileUsingExplicitStarts( - randomData(BACKUP_DATA_SIZE_BYTES), - chunkOrdering -> chunkOrdering, - chunksMetadata -> { - ChunksMetadata metadata = CryptoTestUtils.clone(chunksMetadata); - metadata.checksumType = ChunksMetadataProto.UNKNOWN_CHECKSUM_TYPE; - return metadata; - }); - - assertThrows( - UnsupportedEncryptedFileException.class, - () -> mTask.decryptFile(mInputFile, mFileOutput)); - } - - /** - * Creates an encrypted backup file from the given data. - * - * @param data The plaintext content. - */ - private void createEncryptedFileUsingExplicitStarts(byte[] data) throws Exception { - createEncryptedFileUsingExplicitStarts(data, chunkOrdering -> chunkOrdering); - } - - /** - * Creates an encrypted backup file from the given data. - * - * @param data The plaintext content. - * @param chunkOrderingTransformer Transforms the ordering before it's encrypted. - */ - private void createEncryptedFileUsingExplicitStarts( - byte[] data, Transformer<ChunkOrdering> chunkOrderingTransformer) throws Exception { - createEncryptedFileUsingExplicitStarts( - data, chunkOrderingTransformer, chunksMetadata -> chunksMetadata); - } - - /** - * Creates an encrypted backup file from the given data in mode {@link - * ChunksMetadataProto#EXPLICIT_STARTS}. - * - * @param data The plaintext content. - * @param chunkOrderingTransformer Transforms the ordering before it's encrypted. - * @param chunksMetadataTransformer Transforms the metadata before it's written. - */ - private void createEncryptedFileUsingExplicitStarts( - byte[] data, - Transformer<ChunkOrdering> chunkOrderingTransformer, - Transformer<ChunksMetadata> chunksMetadataTransformer) - throws Exception { - Result result = backupFullData(data); - - ArrayList<EncryptedChunk> chunks = new ArrayList<>(result.getNewChunks()); - Collections.shuffle(chunks); - HashMap<ChunkHash, Integer> startPositions = new HashMap<>(); - - try (FileOutputStream fos = new FileOutputStream(mInputFile); - DataOutputStream dos = new DataOutputStream(fos)) { - int position = 0; - - for (EncryptedChunk chunk : chunks) { - startPositions.put(chunk.key(), position); - dos.write(chunk.nonce()); - dos.write(chunk.encryptedBytes()); - position += chunk.nonce().length + chunk.encryptedBytes().length; - } - - int[] starts = new int[chunks.size()]; - List<ChunkHash> chunkListing = result.getAllChunks(); - - for (int i = 0; i < chunks.size(); i++) { - starts[i] = startPositions.get(chunkListing.get(i)); - } - - ChunkOrdering chunkOrdering = newChunkOrdering(starts, result.getDigest()); - chunkOrdering = chunkOrderingTransformer.accept(chunkOrdering); - - ChunksMetadata metadata = - newChunksMetadata( - ChunksMetadataProto.AES_256_GCM, - ChunksMetadataProto.SHA_256, - ChunksMetadataProto.EXPLICIT_STARTS, - encrypt(chunkOrdering)); - metadata = chunksMetadataTransformer.accept(metadata); - - dos.write(MessageNano.toByteArray(metadata)); - dos.writeLong(position); - } - } - - /** - * Creates an encrypted backup file from the given data in mode {@link - * ChunksMetadataProto#INLINE_LENGTHS}. - * - * @param data The plaintext key value pairs to back up. - * @param chunkOrderingTransformer Transforms the ordering before it's encrypted. - * @param chunksMetadataTransformer Transforms the metadata before it's written. - */ - private void createEncryptedFileUsingInlineLengths( - Set<KeyValuePair> data, - Transformer<ChunkOrdering> chunkOrderingTransformer, - Transformer<ChunksMetadata> chunksMetadataTransformer) - throws Exception { - Result result = backupKvData(data); - - List<EncryptedChunk> chunks = new ArrayList<>(result.getNewChunks()); - System.out.println("we have chunk count " + chunks.size()); - Collections.shuffle(chunks); - - try (FileOutputStream fos = new FileOutputStream(mInputFile); - DataOutputStream dos = new DataOutputStream(fos)) { - for (EncryptedChunk chunk : chunks) { - dos.writeInt(chunk.nonce().length + chunk.encryptedBytes().length); - dos.write(chunk.nonce()); - dos.write(chunk.encryptedBytes()); - } - - ChunkOrdering chunkOrdering = newChunkOrdering(null, result.getDigest()); - chunkOrdering = chunkOrderingTransformer.accept(chunkOrdering); - - ChunksMetadata metadata = - newChunksMetadata( - ChunksMetadataProto.AES_256_GCM, - ChunksMetadataProto.SHA_256, - ChunksMetadataProto.INLINE_LENGTHS, - encrypt(chunkOrdering)); - metadata = chunksMetadataTransformer.accept(metadata); - - int metadataStart = dos.size(); - dos.write(MessageNano.toByteArray(metadata)); - dos.writeLong(metadataStart); - } - } - - /** Performs a full backup of the given data, and returns the chunks. */ - private BackupEncrypter.Result backupFullData(byte[] data) throws Exception { - BackupStreamEncrypter encrypter = - new BackupStreamEncrypter( - new ByteArrayInputStream(data), - MIN_CHUNK_SIZE_BYTES, - MAX_CHUNK_SIZE_BYTES, - AVERAGE_CHUNK_SIZE_BYTES); - return encrypter.backup( - mChunkEncryptionKey, - randomData(FingerprintMixer.SALT_LENGTH_BYTES), - new HashSet<>()); - } - - private Result backupKvData(Set<KeyValuePair> data) throws Exception { - ShadowBackupDataInput.reset(); - for (KeyValuePair pair : data) { - ShadowBackupDataInput.addEntity(pair.key, pair.value); - } - KvBackupEncrypter encrypter = - new KvBackupEncrypter(new BackupDataInput(NULL_FILE_DESCRIPTOR)); - return encrypter.backup( - mChunkEncryptionKey, - randomData(FingerprintMixer.SALT_LENGTH_BYTES), - Collections.EMPTY_SET); - } - - /** Encrypts {@code chunkOrdering} using {@link #mTertiaryKey}. */ - private byte[] encrypt(ChunkOrdering chunkOrdering) throws Exception { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); - byte[] nonce = randomData(GCM_NONCE_LENGTH_BYTES); - cipher.init( - Cipher.ENCRYPT_MODE, - mTertiaryKey, - new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE, nonce)); - byte[] nanoBytes = MessageNano.toByteArray(chunkOrdering); - byte[] encryptedBytes = cipher.doFinal(nanoBytes); - - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - out.write(nonce); - out.write(encryptedBytes); - return out.toByteArray(); - } - } - - /** Returns {@code length} random bytes. */ - private byte[] randomData(int length) { - byte[] data = new byte[length]; - mRandom.nextBytes(data); - return data; - } - - private static ImmutableMap<String, String> asMap(Collection<KeyValuePair> pairs) { - ImmutableMap.Builder<String, String> map = ImmutableMap.builder(); - for (KeyValuePair pair : pairs) { - map.put(pair.key, new String(pair.value, Charset.forName("UTF-8"))); - } - return map.build(); - } - - private interface Transformer<T> { - T accept(T t); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java deleted file mode 100644 index 21c4e07577da..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/BackupStreamEncrypterTest.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.EncryptedChunk; -import com.android.server.backup.testing.CryptoTestUtils; -import com.android.server.backup.testing.RandomInputStream; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -import java.io.ByteArrayInputStream; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Random; - -import javax.crypto.SecretKey; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class BackupStreamEncrypterTest { - private static final int SALT_LENGTH = 32; - private static final int BITS_PER_BYTE = 8; - private static final int BYTES_PER_KILOBYTE = 1024; - private static final int BYTES_PER_MEGABYTE = 1024 * 1024; - private static final int MIN_CHUNK_SIZE = 2 * BYTES_PER_KILOBYTE; - private static final int AVERAGE_CHUNK_SIZE = 4 * BYTES_PER_KILOBYTE; - private static final int MAX_CHUNK_SIZE = 64 * BYTES_PER_KILOBYTE; - private static final int BACKUP_SIZE = 2 * BYTES_PER_MEGABYTE; - private static final int SMALL_BACKUP_SIZE = BYTES_PER_KILOBYTE; - // 16 bytes for the mac. iv is encoded in a separate field. - private static final int BYTES_OVERHEAD_PER_CHUNK = 16; - private static final int MESSAGE_DIGEST_SIZE_IN_BYTES = 256 / BITS_PER_BYTE; - private static final int RANDOM_SEED = 42; - private static final double TOLERANCE = 0.1; - - private Random mRandom; - private SecretKey mSecretKey; - private byte[] mSalt; - - @Before - public void setUp() throws Exception { - mSecretKey = CryptoTestUtils.generateAesKey(); - - mSalt = new byte[SALT_LENGTH]; - // Make these tests deterministic - mRandom = new Random(RANDOM_SEED); - mRandom.nextBytes(mSalt); - } - - @Test - public void testBackup_producesChunksOfTheGivenAverageSize() throws Exception { - BackupEncrypter.Result result = runBackup(BACKUP_SIZE); - - long totalSize = 0; - for (EncryptedChunk chunk : result.getNewChunks()) { - totalSize += chunk.encryptedBytes().length; - } - - double meanSize = totalSize / result.getNewChunks().size(); - double expectedChunkSize = AVERAGE_CHUNK_SIZE + BYTES_OVERHEAD_PER_CHUNK; - assertThat(Math.abs(meanSize - expectedChunkSize) / expectedChunkSize) - .isLessThan(TOLERANCE); - } - - @Test - public void testBackup_producesNoChunksSmallerThanMinSize() throws Exception { - BackupEncrypter.Result result = runBackup(BACKUP_SIZE); - List<EncryptedChunk> chunks = result.getNewChunks(); - - // Last chunk could be smaller, depending on the file size and how it is chunked - for (EncryptedChunk chunk : chunks.subList(0, chunks.size() - 2)) { - assertThat(chunk.encryptedBytes().length) - .isAtLeast(MIN_CHUNK_SIZE + BYTES_OVERHEAD_PER_CHUNK); - } - } - - @Test - public void testBackup_producesNoChunksLargerThanMaxSize() throws Exception { - BackupEncrypter.Result result = runBackup(BACKUP_SIZE); - List<EncryptedChunk> chunks = result.getNewChunks(); - - for (EncryptedChunk chunk : chunks) { - assertThat(chunk.encryptedBytes().length) - .isAtMost(MAX_CHUNK_SIZE + BYTES_OVERHEAD_PER_CHUNK); - } - } - - @Test - public void testBackup_producesAFileOfTheExpectedSize() throws Exception { - BackupEncrypter.Result result = runBackup(BACKUP_SIZE); - HashMap<ChunkHash, EncryptedChunk> chunksBySha256 = - chunksIndexedByKey(result.getNewChunks()); - - int expectedSize = BACKUP_SIZE + result.getAllChunks().size() * BYTES_OVERHEAD_PER_CHUNK; - int size = 0; - for (ChunkHash byteString : result.getAllChunks()) { - size += chunksBySha256.get(byteString).encryptedBytes().length; - } - assertThat(size).isEqualTo(expectedSize); - } - - @Test - public void testBackup_forSameFile_producesNoNewChunks() throws Exception { - byte[] backupData = getRandomData(BACKUP_SIZE); - BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of()); - - BackupEncrypter.Result incrementalResult = runBackup(backupData, result.getAllChunks()); - - assertThat(incrementalResult.getNewChunks()).isEmpty(); - } - - @Test - public void testBackup_onlyUpdatesChangedChunks() throws Exception { - byte[] backupData = getRandomData(BACKUP_SIZE); - BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of()); - - // Let's update the 2nd and 5th chunk - backupData[positionOfChunk(result, 1)]++; - backupData[positionOfChunk(result, 4)]++; - BackupEncrypter.Result incrementalResult = runBackup(backupData, result.getAllChunks()); - - assertThat(incrementalResult.getNewChunks()).hasSize(2); - } - - @Test - public void testBackup_doesNotIncludeUpdatedChunksInNewListing() throws Exception { - byte[] backupData = getRandomData(BACKUP_SIZE); - BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of()); - - // Let's update the 2nd and 5th chunk - backupData[positionOfChunk(result, 1)]++; - backupData[positionOfChunk(result, 4)]++; - BackupEncrypter.Result incrementalResult = runBackup(backupData, result.getAllChunks()); - - List<EncryptedChunk> newChunks = incrementalResult.getNewChunks(); - List<ChunkHash> chunkListing = result.getAllChunks(); - assertThat(newChunks).doesNotContain(chunkListing.get(1)); - assertThat(newChunks).doesNotContain(chunkListing.get(4)); - } - - @Test - public void testBackup_includesUnchangedChunksInNewListing() throws Exception { - byte[] backupData = getRandomData(BACKUP_SIZE); - BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of()); - - // Let's update the 2nd and 5th chunk - backupData[positionOfChunk(result, 1)]++; - backupData[positionOfChunk(result, 4)]++; - BackupEncrypter.Result incrementalResult = runBackup(backupData, result.getAllChunks()); - - HashSet<ChunkHash> chunksPresentInIncremental = - new HashSet<>(incrementalResult.getAllChunks()); - chunksPresentInIncremental.removeAll(result.getAllChunks()); - - assertThat(chunksPresentInIncremental).hasSize(2); - } - - @Test - public void testBackup_forSameData_createsSameDigest() throws Exception { - byte[] backupData = getRandomData(SMALL_BACKUP_SIZE); - - BackupEncrypter.Result result = runBackup(backupData, ImmutableList.of()); - BackupEncrypter.Result result2 = runBackup(backupData, ImmutableList.of()); - assertThat(result.getDigest()).isEqualTo(result2.getDigest()); - } - - @Test - public void testBackup_forDifferentData_createsDifferentDigest() throws Exception { - byte[] backup1Data = getRandomData(SMALL_BACKUP_SIZE); - byte[] backup2Data = getRandomData(SMALL_BACKUP_SIZE); - - BackupEncrypter.Result result = runBackup(backup1Data, ImmutableList.of()); - BackupEncrypter.Result result2 = runBackup(backup2Data, ImmutableList.of()); - assertThat(result.getDigest()).isNotEqualTo(result2.getDigest()); - } - - @Test - public void testBackup_createsDigestOf32Bytes() throws Exception { - assertThat(runBackup(getRandomData(SMALL_BACKUP_SIZE), ImmutableList.of()).getDigest()) - .hasLength(MESSAGE_DIGEST_SIZE_IN_BYTES); - } - - private byte[] getRandomData(int size) throws Exception { - RandomInputStream randomInputStream = new RandomInputStream(mRandom, size); - byte[] backupData = new byte[size]; - randomInputStream.read(backupData); - return backupData; - } - - private BackupEncrypter.Result runBackup(int backupSize) throws Exception { - RandomInputStream dataStream = new RandomInputStream(mRandom, backupSize); - BackupStreamEncrypter task = - new BackupStreamEncrypter( - dataStream, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE, AVERAGE_CHUNK_SIZE); - return task.backup(mSecretKey, mSalt, ImmutableSet.of()); - } - - private BackupEncrypter.Result runBackup(byte[] data, List<ChunkHash> existingChunks) - throws Exception { - ByteArrayInputStream dataStream = new ByteArrayInputStream(data); - BackupStreamEncrypter task = - new BackupStreamEncrypter( - dataStream, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE, AVERAGE_CHUNK_SIZE); - return task.backup(mSecretKey, mSalt, ImmutableSet.copyOf(existingChunks)); - } - - /** Returns a {@link HashMap} of the chunks, indexed by the SHA-256 Mac key. */ - private static HashMap<ChunkHash, EncryptedChunk> chunksIndexedByKey( - List<EncryptedChunk> chunks) { - HashMap<ChunkHash, EncryptedChunk> chunksByKey = new HashMap<>(); - for (EncryptedChunk chunk : chunks) { - chunksByKey.put(chunk.key(), chunk); - } - return chunksByKey; - } - - /** - * Returns the start position of the chunk in the plaintext backup data. - * - * @param result The result from a backup. - * @param index The index of the chunk in question. - * @return the start position. - */ - private static int positionOfChunk(BackupEncrypter.Result result, int index) { - HashMap<ChunkHash, EncryptedChunk> byKey = chunksIndexedByKey(result.getNewChunks()); - List<ChunkHash> listing = result.getAllChunks(); - - int position = 0; - for (int i = 0; i < index - 1; i++) { - EncryptedChunk chunk = byKey.get(listing.get(i)); - position += chunk.encryptedBytes().length - BYTES_OVERHEAD_PER_CHUNK; - } - - return position; - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTaskTest.java deleted file mode 100644 index 81bfce1da294..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/ClearCryptoStateTaskTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.spy; - -import android.content.Context; -import android.platform.test.annotations.Presubmit; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.server.backup.encryption.CryptoSettings; -import com.android.server.backup.encryption.chunking.ProtoStore; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing; -import com.android.server.backup.encryption.protos.nano.KeyValueListingProto.KeyValueListing; -import com.android.server.backup.encryption.storage.BackupEncryptionDb; -import com.android.server.backup.encryption.storage.TertiaryKey; -import com.android.server.backup.encryption.storage.TertiaryKeysTable; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class ClearCryptoStateTaskTest { - private static final String TEST_PACKAGE_NAME = "com.android.example"; - - private ClearCryptoStateTask mClearCryptoStateTask; - private CryptoSettings mCryptoSettings; - private Context mApplication; - - @Before - public void setUp() { - mApplication = ApplicationProvider.getApplicationContext(); - mCryptoSettings = spy(CryptoSettings.getInstanceForTesting(mApplication)); - mClearCryptoStateTask = new ClearCryptoStateTask(mApplication, mCryptoSettings); - } - - @Test - public void run_clearsChunkListingProtoState() throws Exception { - String packageName = TEST_PACKAGE_NAME; - ChunkListing chunkListing = new ChunkListing(); - ProtoStore.createChunkListingStore(mApplication).saveProto(packageName, chunkListing); - - mClearCryptoStateTask.run(); - - assertThat( - ProtoStore.createChunkListingStore(mApplication) - .loadProto(packageName) - .isPresent()) - .isFalse(); - } - - @Test - public void run_clearsKeyValueProtoState() throws Exception { - String packageName = TEST_PACKAGE_NAME; - KeyValueListing keyValueListing = new KeyValueListing(); - ProtoStore.createKeyValueListingStore(mApplication).saveProto(packageName, keyValueListing); - - mClearCryptoStateTask.run(); - - assertThat( - ProtoStore.createKeyValueListingStore(mApplication) - .loadProto(packageName) - .isPresent()) - .isFalse(); - } - - @Test - public void run_clearsTertiaryKeysTable() throws Exception { - String secondaryKeyAlias = "bob"; - TertiaryKeysTable tertiaryKeysTable = - BackupEncryptionDb.newInstance(mApplication).getTertiaryKeysTable(); - tertiaryKeysTable.addKey( - new TertiaryKey( - secondaryKeyAlias, "packageName", /*wrappedKeyBytes=*/ new byte[0])); - - mClearCryptoStateTask.run(); - - assertThat(tertiaryKeysTable.getAllKeys(secondaryKeyAlias)).isEmpty(); - } - - @Test - public void run_clearsSettings() { - mCryptoSettings.setSecondaryLastRotated(100001); - - mClearCryptoStateTask.run(); - - assertThat(mCryptoSettings.getSecondaryLastRotated().isPresent()).isFalse(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java deleted file mode 100644 index 23d6e34db4f1..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedBackupTaskTest.java +++ /dev/null @@ -1,397 +0,0 @@ -/* - * 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 com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.AES_256_GCM; -import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED; -import static com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.SHA_256; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.BackupFileBuilder; -import com.android.server.backup.encryption.chunking.EncryptedChunk; -import com.android.server.backup.encryption.chunking.EncryptedChunkEncoder; -import com.android.server.backup.encryption.chunking.LengthlessEncryptedChunkEncoder; -import com.android.server.backup.encryption.client.CryptoBackupServer; -import com.android.server.backup.encryption.keys.TertiaryKeyGenerator; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkOrdering; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunksMetadata; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto.WrappedKey; -import com.android.server.backup.encryption.tasks.BackupEncrypter.Result; -import com.android.server.backup.testing.CryptoTestUtils; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.protobuf.nano.MessageNano; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -import java.io.OutputStream; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.concurrent.CancellationException; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.GCMParameterSpec; - -@Config(shadows = {EncryptedBackupTaskTest.ShadowBackupFileBuilder.class}) -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class EncryptedBackupTaskTest { - - private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; - private static final int GCM_NONCE_LENGTH_BYTES = 12; - private static final int GCM_TAG_LENGTH_BYTES = 16; - private static final int BITS_PER_BYTE = 8; - - private static final byte[] TEST_FINGERPRINT_MIXER_SALT = - Arrays.copyOf(new byte[] {22}, ChunkHash.HASH_LENGTH_BYTES); - - private static final byte[] TEST_NONCE = - Arrays.copyOf(new byte[] {55}, EncryptedChunk.NONCE_LENGTH_BYTES); - - private static final ChunkHash TEST_HASH_1 = - new ChunkHash(Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES)); - private static final ChunkHash TEST_HASH_2 = - new ChunkHash(Arrays.copyOf(new byte[] {2}, ChunkHash.HASH_LENGTH_BYTES)); - private static final ChunkHash TEST_HASH_3 = - new ChunkHash(Arrays.copyOf(new byte[] {3}, ChunkHash.HASH_LENGTH_BYTES)); - - private static final EncryptedChunk TEST_CHUNK_1 = - EncryptedChunk.create(TEST_HASH_1, TEST_NONCE, new byte[] {1, 2, 3, 4, 5}); - private static final EncryptedChunk TEST_CHUNK_2 = - EncryptedChunk.create(TEST_HASH_2, TEST_NONCE, new byte[] {6, 7, 8, 9, 10}); - private static final EncryptedChunk TEST_CHUNK_3 = - EncryptedChunk.create(TEST_HASH_3, TEST_NONCE, new byte[] {11, 12, 13, 14, 15}); - - private static final byte[] TEST_CHECKSUM = Arrays.copyOf(new byte[] {10}, 258 / 8); - private static final String TEST_PACKAGE_NAME = "com.example.package"; - private static final String TEST_OLD_DOCUMENT_ID = "old_doc_1"; - private static final String TEST_NEW_DOCUMENT_ID = "new_doc_1"; - - @Captor private ArgumentCaptor<ChunksMetadata> mMetadataCaptor; - - @Mock private CryptoBackupServer mCryptoBackupServer; - @Mock private BackupEncrypter mBackupEncrypter; - @Mock private BackupFileBuilder mBackupFileBuilder; - - private ChunkListing mOldChunkListing; - private SecretKey mTertiaryKey; - private WrappedKey mWrappedTertiaryKey; - private EncryptedChunkEncoder mEncryptedChunkEncoder; - private EncryptedBackupTask mTask; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - SecureRandom secureRandom = new SecureRandom(); - mTertiaryKey = new TertiaryKeyGenerator(secureRandom).generate(); - mWrappedTertiaryKey = new WrappedKey(); - - mEncryptedChunkEncoder = new LengthlessEncryptedChunkEncoder(); - - ShadowBackupFileBuilder.sInstance = mBackupFileBuilder; - - mTask = - new EncryptedBackupTask( - mCryptoBackupServer, secureRandom, TEST_PACKAGE_NAME, mBackupEncrypter); - } - - @Test - public void performNonIncrementalBackup_performsBackup() throws Exception { - setUpWithoutExistingBackup(); - - // Chunk listing and ordering don't matter for this test. - when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing()); - when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering()); - - when(mCryptoBackupServer.uploadNonIncrementalBackup(eq(TEST_PACKAGE_NAME), any(), any())) - .thenReturn(TEST_NEW_DOCUMENT_ID); - - mTask.performNonIncrementalBackup( - mTertiaryKey, mWrappedTertiaryKey, TEST_FINGERPRINT_MIXER_SALT); - - verify(mBackupFileBuilder) - .writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2), - ImmutableMap.of(TEST_HASH_1, TEST_CHUNK_1, TEST_HASH_2, TEST_CHUNK_2)); - verify(mBackupFileBuilder).finish(any()); - verify(mCryptoBackupServer) - .uploadNonIncrementalBackup(eq(TEST_PACKAGE_NAME), any(), eq(mWrappedTertiaryKey)); - } - - @Test - public void performIncrementalBackup_performsBackup() throws Exception { - setUpWithExistingBackup(); - - // Chunk listing and ordering don't matter for this test. - when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing()); - when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering()); - - when(mCryptoBackupServer.uploadIncrementalBackup( - eq(TEST_PACKAGE_NAME), eq(TEST_OLD_DOCUMENT_ID), any(), any())) - .thenReturn(TEST_NEW_DOCUMENT_ID); - - mTask.performIncrementalBackup(mTertiaryKey, mWrappedTertiaryKey, mOldChunkListing); - - verify(mBackupFileBuilder) - .writeChunks( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_3), - ImmutableMap.of(TEST_HASH_2, TEST_CHUNK_2)); - verify(mBackupFileBuilder).finish(any()); - verify(mCryptoBackupServer) - .uploadIncrementalBackup( - eq(TEST_PACKAGE_NAME), - eq(TEST_OLD_DOCUMENT_ID), - any(), - eq(mWrappedTertiaryKey)); - } - - @Test - public void performIncrementalBackup_returnsNewChunkListingWithDocId() throws Exception { - setUpWithExistingBackup(); - - ChunkListing chunkListingWithoutDocId = - CryptoTestUtils.newChunkListingWithoutDocId( - TEST_FINGERPRINT_MIXER_SALT, - AES_256_GCM, - CHUNK_ORDERING_TYPE_UNSPECIFIED, - createChunkProtoFor(TEST_HASH_1, TEST_CHUNK_1), - createChunkProtoFor(TEST_HASH_2, TEST_CHUNK_2)); - when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(chunkListingWithoutDocId); - - // Chunk ordering doesn't matter for this test. - when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering()); - - when(mCryptoBackupServer.uploadIncrementalBackup( - eq(TEST_PACKAGE_NAME), eq(TEST_OLD_DOCUMENT_ID), any(), any())) - .thenReturn(TEST_NEW_DOCUMENT_ID); - - ChunkListing actualChunkListing = - mTask.performIncrementalBackup(mTertiaryKey, mWrappedTertiaryKey, mOldChunkListing); - - ChunkListing expectedChunkListing = CryptoTestUtils.clone(chunkListingWithoutDocId); - expectedChunkListing.documentId = TEST_NEW_DOCUMENT_ID; - assertChunkListingsAreEqual(actualChunkListing, expectedChunkListing); - } - - @Test - public void performNonIncrementalBackup_returnsNewChunkListingWithDocId() throws Exception { - setUpWithoutExistingBackup(); - - ChunkListing chunkListingWithoutDocId = - CryptoTestUtils.newChunkListingWithoutDocId( - TEST_FINGERPRINT_MIXER_SALT, - AES_256_GCM, - CHUNK_ORDERING_TYPE_UNSPECIFIED, - createChunkProtoFor(TEST_HASH_1, TEST_CHUNK_1), - createChunkProtoFor(TEST_HASH_2, TEST_CHUNK_2)); - when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(chunkListingWithoutDocId); - - // Chunk ordering doesn't matter for this test. - when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering()); - - when(mCryptoBackupServer.uploadNonIncrementalBackup(eq(TEST_PACKAGE_NAME), any(), any())) - .thenReturn(TEST_NEW_DOCUMENT_ID); - - ChunkListing actualChunkListing = - mTask.performNonIncrementalBackup( - mTertiaryKey, mWrappedTertiaryKey, TEST_FINGERPRINT_MIXER_SALT); - - ChunkListing expectedChunkListing = CryptoTestUtils.clone(chunkListingWithoutDocId); - expectedChunkListing.documentId = TEST_NEW_DOCUMENT_ID; - assertChunkListingsAreEqual(actualChunkListing, expectedChunkListing); - } - - @Test - public void performNonIncrementalBackup_buildsCorrectChunkMetadata() throws Exception { - setUpWithoutExistingBackup(); - - // Chunk listing doesn't matter for this test. - when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing()); - - ChunkOrdering expectedOrdering = - CryptoTestUtils.newChunkOrdering(new int[10], TEST_CHECKSUM); - when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(expectedOrdering); - - when(mCryptoBackupServer.uploadNonIncrementalBackup(eq(TEST_PACKAGE_NAME), any(), any())) - .thenReturn(TEST_NEW_DOCUMENT_ID); - - mTask.performNonIncrementalBackup( - mTertiaryKey, mWrappedTertiaryKey, TEST_FINGERPRINT_MIXER_SALT); - - verify(mBackupFileBuilder).finish(mMetadataCaptor.capture()); - - ChunksMetadata actualMetadata = mMetadataCaptor.getValue(); - assertThat(actualMetadata.checksumType).isEqualTo(SHA_256); - assertThat(actualMetadata.cipherType).isEqualTo(AES_256_GCM); - - ChunkOrdering actualOrdering = decryptChunkOrdering(actualMetadata.chunkOrdering); - assertThat(actualOrdering.checksum).isEqualTo(TEST_CHECKSUM); - assertThat(actualOrdering.starts).isEqualTo(expectedOrdering.starts); - } - - @Test - public void cancel_incrementalBackup_doesNotUploadOrSaveChunkListing() throws Exception { - setUpWithExistingBackup(); - - // Chunk listing and ordering don't matter for this test. - when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing()); - when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering()); - - mTask.cancel(); - assertThrows( - CancellationException.class, - () -> - mTask.performIncrementalBackup( - mTertiaryKey, mWrappedTertiaryKey, mOldChunkListing)); - - verify(mCryptoBackupServer, never()).uploadIncrementalBackup(any(), any(), any(), any()); - verify(mCryptoBackupServer, never()).uploadNonIncrementalBackup(any(), any(), any()); - } - - @Test - public void cancel_nonIncrementalBackup_doesNotUploadOrSaveChunkListing() throws Exception { - setUpWithoutExistingBackup(); - - // Chunk listing and ordering don't matter for this test. - when(mBackupFileBuilder.getNewChunkListing(any())).thenReturn(new ChunkListing()); - when(mBackupFileBuilder.getNewChunkOrdering(TEST_CHECKSUM)).thenReturn(new ChunkOrdering()); - - mTask.cancel(); - assertThrows( - CancellationException.class, - () -> - mTask.performNonIncrementalBackup( - mTertiaryKey, mWrappedTertiaryKey, TEST_FINGERPRINT_MIXER_SALT)); - - verify(mCryptoBackupServer, never()).uploadIncrementalBackup(any(), any(), any(), any()); - verify(mCryptoBackupServer, never()).uploadNonIncrementalBackup(any(), any(), any()); - } - - /** Sets up a backup of [CHUNK 1][CHUNK 2] with no existing data. */ - private void setUpWithoutExistingBackup() throws Exception { - Result result = - new Result( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2), - ImmutableList.of(TEST_CHUNK_1, TEST_CHUNK_2), - TEST_CHECKSUM); - when(mBackupEncrypter.backup(any(), eq(TEST_FINGERPRINT_MIXER_SALT), eq(ImmutableSet.of()))) - .thenReturn(result); - } - - /** - * Sets up a backup of [CHUNK 1][CHUNK 2][CHUNK 3] where the previous backup contained [CHUNK - * 1][CHUNK 3]. - */ - private void setUpWithExistingBackup() throws Exception { - mOldChunkListing = - CryptoTestUtils.newChunkListing( - TEST_OLD_DOCUMENT_ID, - TEST_FINGERPRINT_MIXER_SALT, - AES_256_GCM, - CHUNK_ORDERING_TYPE_UNSPECIFIED, - createChunkProtoFor(TEST_HASH_1, TEST_CHUNK_1), - createChunkProtoFor(TEST_HASH_3, TEST_CHUNK_3)); - - Result result = - new Result( - ImmutableList.of(TEST_HASH_1, TEST_HASH_2, TEST_HASH_3), - ImmutableList.of(TEST_CHUNK_2), - TEST_CHECKSUM); - when(mBackupEncrypter.backup( - any(), - eq(TEST_FINGERPRINT_MIXER_SALT), - eq(ImmutableSet.of(TEST_HASH_1, TEST_HASH_3)))) - .thenReturn(result); - } - - private ChunksMetadataProto.Chunk createChunkProtoFor( - ChunkHash chunkHash, EncryptedChunk encryptedChunk) { - return CryptoTestUtils.newChunk( - chunkHash, mEncryptedChunkEncoder.getEncodedLengthOfChunk(encryptedChunk)); - } - - private ChunkOrdering decryptChunkOrdering(byte[] encryptedOrdering) throws Exception { - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init( - Cipher.DECRYPT_MODE, - mTertiaryKey, - new GCMParameterSpec( - GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE, - encryptedOrdering, - /*offset=*/ 0, - GCM_NONCE_LENGTH_BYTES)); - byte[] decrypted = - cipher.doFinal( - encryptedOrdering, - GCM_NONCE_LENGTH_BYTES, - encryptedOrdering.length - GCM_NONCE_LENGTH_BYTES); - return ChunkOrdering.parseFrom(decrypted); - } - - // This method is needed because nano protobuf generated classes dont implmenent - // .equals - private void assertChunkListingsAreEqual(ChunkListing a, ChunkListing b) { - byte[] aBytes = MessageNano.toByteArray(a); - byte[] bBytes = MessageNano.toByteArray(b); - - assertThat(aBytes).isEqualTo(bBytes); - } - - @Implements(BackupFileBuilder.class) - public static class ShadowBackupFileBuilder { - - private static BackupFileBuilder sInstance; - - @Implementation - public static BackupFileBuilder createForNonIncremental(OutputStream outputStream) { - return sInstance; - } - - @Implementation - public static BackupFileBuilder createForIncremental( - OutputStream outputStream, ChunkListing oldChunkListing) { - return sInstance; - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessorTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessorTest.java deleted file mode 100644 index 675d03fb9869..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessorTest.java +++ /dev/null @@ -1,387 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.testng.Assert.assertThrows; - -import android.annotation.Nullable; -import android.app.backup.BackupTransport; -import android.platform.test.annotations.Presubmit; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.server.backup.encryption.FullBackupDataProcessor; -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.TertiaryKeyManager; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.testing.QueuingNonAutomaticExecutorService; - -import com.google.common.io.ByteStreams; -import com.google.common.primitives.Bytes; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.SecureRandom; - -import javax.crypto.spec.SecretKeySpec; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -@Config( - shadows = { - EncryptedFullBackupDataProcessorTest.ShadowEncryptedFullBackupTask.class, - }) -public class EncryptedFullBackupDataProcessorTest { - - private static final String KEY_GENERATOR_ALGORITHM = "AES"; - - private static final String TEST_PACKAGE = "com.example.app1"; - private static final byte[] TEST_DATA_1 = {1, 2, 3, 4}; - private static final byte[] TEST_DATA_2 = {5, 6, 7, 8}; - - private final RecoverableKeyStoreSecondaryKey mTestSecondaryKey = - new RecoverableKeyStoreSecondaryKey( - /*alias=*/ "test_key", - new SecretKeySpec( - new byte[] { - 1, 2, 3, - }, - KEY_GENERATOR_ALGORITHM)); - - private QueuingNonAutomaticExecutorService mExecutorService; - private FullBackupDataProcessor mFullBackupDataProcessor; - @Mock private FullBackupDataProcessor.FullBackupCallbacks mFullBackupCallbacks; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - mExecutorService = new QueuingNonAutomaticExecutorService(); - mFullBackupDataProcessor = - new EncryptedFullBackupDataProcessor( - ApplicationProvider.getApplicationContext(), - mExecutorService, - mock(CryptoBackupServer.class), - new SecureRandom(), - mTestSecondaryKey, - TEST_PACKAGE); - } - - @After - public void tearDown() { - ShadowEncryptedFullBackupTask.reset(); - } - - @Test - public void initiate_callTwice_throws() throws Exception { - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(new byte[10])); - - assertThrows( - IllegalStateException.class, - () -> mFullBackupDataProcessor.initiate(new ByteArrayInputStream(new byte[10]))); - } - - @Test - public void pushData_writesDataToTask() throws Exception { - byte[] inputData = Bytes.concat(TEST_DATA_1, TEST_DATA_2); - - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(inputData)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - mFullBackupDataProcessor.pushData(TEST_DATA_2.length); - finishBackupTask(); - mFullBackupDataProcessor.finish(); - - byte[] result = ByteStreams.toByteArray(ShadowEncryptedFullBackupTask.sInputStream); - assertThat(result).isEqualTo(Bytes.concat(TEST_DATA_1, TEST_DATA_2)); - } - - @Test - public void pushData_noError_returnsOk() throws Exception { - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - int result = mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTask(); - mFullBackupDataProcessor.finish(); - - assertThat(result).isEqualTo(BackupTransport.TRANSPORT_OK); - } - - @Test - public void pushData_ioExceptionOnCopy_returnsError() throws Exception { - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - - // Close the stream so there's an IO error when the processor tries to write to it. - ShadowEncryptedFullBackupTask.sInputStream.close(); - int result = mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - - finishBackupTask(); - mFullBackupDataProcessor.finish(); - - assertThat(result).isEqualTo(BackupTransport.TRANSPORT_ERROR); - } - - @Test - public void pushData_exceptionDuringUpload_returnsError() throws Exception { - byte[] inputData = Bytes.concat(TEST_DATA_1, TEST_DATA_2); - - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(inputData)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTaskWithException(new IOException("Test exception")); - int result = mFullBackupDataProcessor.pushData(TEST_DATA_2.length); - - assertThat(result).isEqualTo(BackupTransport.TRANSPORT_ERROR); - } - - @Test - public void pushData_quotaExceptionDuringUpload_doesNotLogAndReturnsQuotaExceeded() - throws Exception { - mFullBackupDataProcessor.attachCallbacks(mFullBackupCallbacks); - byte[] inputData = Bytes.concat(TEST_DATA_1, TEST_DATA_2); - - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(inputData)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTaskWithException(new SizeQuotaExceededException()); - int result = mFullBackupDataProcessor.pushData(TEST_DATA_2.length); - - assertThat(result).isEqualTo(BackupTransport.TRANSPORT_QUOTA_EXCEEDED); - - verify(mFullBackupCallbacks, never()).onSuccess(); - verify(mFullBackupCallbacks, never()) - .onTransferFailed(); // FullBackupSession will handle this. - } - - @Test - public void pushData_unexpectedEncryptedBackup_logs() throws Exception { - byte[] inputData = Bytes.concat(TEST_DATA_1, TEST_DATA_2); - - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(inputData)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTaskWithException(new GeneralSecurityException()); - int result = mFullBackupDataProcessor.pushData(TEST_DATA_2.length); - - assertThat(result).isEqualTo(BackupTransport.TRANSPORT_ERROR); - } - - @Test - public void pushData_permanentExceptionDuringUpload_callsErrorCallback() throws Exception { - mFullBackupDataProcessor.attachCallbacks(mFullBackupCallbacks); - byte[] inputData = Bytes.concat(TEST_DATA_1, TEST_DATA_2); - - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(inputData)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTaskWithException(new IOException()); - mFullBackupDataProcessor.pushData(TEST_DATA_2.length); - - verify(mFullBackupCallbacks, never()).onSuccess(); - verify(mFullBackupCallbacks).onTransferFailed(); - } - - @Test - public void pushData_beforeInitiate_throws() { - assertThrows( - IllegalStateException.class, - () -> mFullBackupDataProcessor.pushData(/*numBytes=*/ 10)); - } - - @Test - public void cancel_cancelsTask() throws Exception { - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - mFullBackupDataProcessor.cancel(); - - assertThat(ShadowEncryptedFullBackupTask.sCancelled).isTrue(); - } - - @Test - public void cancel_beforeInitiate_throws() { - assertThrows(IllegalStateException.class, () -> mFullBackupDataProcessor.cancel()); - } - - @Test - public void finish_noException_returnsTransportOk() throws Exception { - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTask(); - int result = mFullBackupDataProcessor.finish(); - - assertThat(result).isEqualTo(BackupTransport.TRANSPORT_OK); - } - - @Test - public void finish_exceptionDuringUpload_returnsTransportError() throws Exception { - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTaskWithException(new IOException("Test exception")); - int result = mFullBackupDataProcessor.finish(); - - assertThat(result).isEqualTo(BackupTransport.TRANSPORT_ERROR); - } - - @Test - public void finish_successfulBackup_callsSuccessCallback() throws Exception { - mFullBackupDataProcessor.attachCallbacks(mFullBackupCallbacks); - - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTask(); - mFullBackupDataProcessor.finish(); - - verify(mFullBackupCallbacks).onSuccess(); - verify(mFullBackupCallbacks, never()).onTransferFailed(); - } - - @Test - public void finish_backupFailedWithPermanentError_callsErrorCallback() throws Exception { - mFullBackupDataProcessor.attachCallbacks(mFullBackupCallbacks); - - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTaskWithException(new IOException()); - mFullBackupDataProcessor.finish(); - - verify(mFullBackupCallbacks, never()).onSuccess(); - verify(mFullBackupCallbacks).onTransferFailed(); - } - - @Test - public void finish_backupFailedWithQuotaException_doesNotCallbackAndReturnsQuotaExceeded() - throws Exception { - mFullBackupDataProcessor.attachCallbacks(mFullBackupCallbacks); - - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - finishBackupTaskWithException(new SizeQuotaExceededException()); - int result = mFullBackupDataProcessor.finish(); - - assertThat(result).isEqualTo(BackupTransport.TRANSPORT_QUOTA_EXCEEDED); - verify(mFullBackupCallbacks, never()).onSuccess(); - verify(mFullBackupCallbacks, never()) - .onTransferFailed(); // FullBackupSession will handle this. - } - - @Test - public void finish_beforeInitiate_throws() { - assertThrows(IllegalStateException.class, () -> mFullBackupDataProcessor.finish()); - } - - @Test - public void handleCheckSizeRejectionZeroBytes_cancelsTask() throws Exception { - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(new byte[10])); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.handleCheckSizeRejectionZeroBytes(); - - assertThat(ShadowEncryptedFullBackupTask.sCancelled).isTrue(); - } - - @Test - public void handleCheckSizeRejectionQuotaExceeded_cancelsTask() throws Exception { - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - mFullBackupDataProcessor.handleCheckSizeRejectionQuotaExceeded(); - - assertThat(ShadowEncryptedFullBackupTask.sCancelled).isTrue(); - } - - @Test - public void handleSendBytesQuotaExceeded_cancelsTask() throws Exception { - mFullBackupDataProcessor.initiate(new ByteArrayInputStream(TEST_DATA_1)); - mFullBackupDataProcessor.start(); - mFullBackupDataProcessor.pushData(TEST_DATA_1.length); - mFullBackupDataProcessor.handleSendBytesQuotaExceeded(); - - assertThat(ShadowEncryptedFullBackupTask.sCancelled).isTrue(); - } - - private void finishBackupTask() { - mExecutorService.runNext(); - } - - private void finishBackupTaskWithException(Exception exception) { - ShadowEncryptedFullBackupTask.sOnCallException = exception; - finishBackupTask(); - } - - @Implements(EncryptedFullBackupTask.class) - public static class ShadowEncryptedFullBackupTask { - - private static InputStream sInputStream; - @Nullable private static Exception sOnCallException; - private static boolean sCancelled; - - public void __constructor__( - ProtoStore<ChunksMetadataProto.ChunkListing> chunkListingStore, - TertiaryKeyManager tertiaryKeyManager, - EncryptedBackupTask task, - InputStream inputStream, - String packageName, - SecureRandom secureRandom) { - sInputStream = inputStream; - } - - @Implementation - public Void call() throws Exception { - if (sOnCallException != null) { - throw sOnCallException; - } - - return null; - } - - @Implementation - public void cancel() { - sCancelled = true; - } - - public static void reset() { - sOnCallException = null; - sCancelled = false; - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java deleted file mode 100644 index bfc5d0dca3ff..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupTaskTest.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.ProtoStore; -import com.android.server.backup.encryption.chunking.cdc.FingerprintMixer; -import com.android.server.backup.encryption.keys.TertiaryKeyManager; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto.ChunkListing; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto.WrappedKey; -import com.android.server.backup.testing.CryptoTestUtils; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Optional; - -import javax.crypto.SecretKey; - -@Config(shadows = {EncryptedBackupTaskTest.ShadowBackupFileBuilder.class}) -@RunWith(RobolectricTestRunner.class) -public class EncryptedFullBackupTaskTest { - private static final String TEST_PACKAGE_NAME = "com.example.package"; - private static final byte[] TEST_EXISTING_FINGERPRINT_MIXER_SALT = - Arrays.copyOf(new byte[] {11}, ChunkHash.HASH_LENGTH_BYTES); - private static final byte[] TEST_GENERATED_FINGERPRINT_MIXER_SALT = - Arrays.copyOf(new byte[] {22}, ChunkHash.HASH_LENGTH_BYTES); - private static final ChunkHash TEST_CHUNK_HASH_1 = - new ChunkHash(Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES)); - private static final ChunkHash TEST_CHUNK_HASH_2 = - new ChunkHash(Arrays.copyOf(new byte[] {2}, ChunkHash.HASH_LENGTH_BYTES)); - private static final int TEST_CHUNK_LENGTH_1 = 20; - private static final int TEST_CHUNK_LENGTH_2 = 40; - - @Mock private ProtoStore<ChunkListing> mChunkListingStore; - @Mock private TertiaryKeyManager mTertiaryKeyManager; - @Mock private InputStream mInputStream; - @Mock private EncryptedBackupTask mEncryptedBackupTask; - @Mock private SecretKey mTertiaryKey; - @Mock private SecureRandom mSecureRandom; - - private EncryptedFullBackupTask mTask; - private ChunkListing mOldChunkListing; - private ChunkListing mNewChunkListing; - private WrappedKey mWrappedTertiaryKey; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mWrappedTertiaryKey = new WrappedKey(); - when(mTertiaryKeyManager.getKey()).thenReturn(mTertiaryKey); - when(mTertiaryKeyManager.getWrappedKey()).thenReturn(mWrappedTertiaryKey); - - mOldChunkListing = - CryptoTestUtils.newChunkListing( - /* docId */ null, - TEST_EXISTING_FINGERPRINT_MIXER_SALT, - ChunksMetadataProto.AES_256_GCM, - ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED, - CryptoTestUtils.newChunk(TEST_CHUNK_HASH_1.getHash(), TEST_CHUNK_LENGTH_1)); - mNewChunkListing = - CryptoTestUtils.newChunkListing( - /* docId */ null, - /* fingerprintSalt */ null, - ChunksMetadataProto.AES_256_GCM, - ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED, - CryptoTestUtils.newChunk(TEST_CHUNK_HASH_1.getHash(), TEST_CHUNK_LENGTH_1), - CryptoTestUtils.newChunk(TEST_CHUNK_HASH_2.getHash(), TEST_CHUNK_LENGTH_2)); - when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any())) - .thenReturn(mNewChunkListing); - when(mEncryptedBackupTask.performIncrementalBackup(any(), any(), any())) - .thenReturn(mNewChunkListing); - when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty()); - - doAnswer(invocation -> { - byte[] byteArray = (byte[]) invocation.getArguments()[0]; - System.arraycopy( - TEST_GENERATED_FINGERPRINT_MIXER_SALT, - /* srcPos */ 0, - byteArray, - /* destPos */ 0, - FingerprintMixer.SALT_LENGTH_BYTES); - return null; - }) - .when(mSecureRandom) - .nextBytes(any(byte[].class)); - - mTask = - new EncryptedFullBackupTask( - mChunkListingStore, - mTertiaryKeyManager, - mEncryptedBackupTask, - mInputStream, - TEST_PACKAGE_NAME, - mSecureRandom); - } - - @Test - public void call_existingChunkListingButTertiaryKeyRotated_performsNonIncrementalBackup() - throws Exception { - when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true); - when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)) - .thenReturn(Optional.of(mOldChunkListing)); - - mTask.call(); - - verify(mEncryptedBackupTask) - .performNonIncrementalBackup( - eq(mTertiaryKey), - eq(mWrappedTertiaryKey), - eq(TEST_GENERATED_FINGERPRINT_MIXER_SALT)); - } - - @Test - public void call_noExistingChunkListing_performsNonIncrementalBackup() throws Exception { - when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty()); - mTask.call(); - verify(mEncryptedBackupTask) - .performNonIncrementalBackup( - eq(mTertiaryKey), - eq(mWrappedTertiaryKey), - eq(TEST_GENERATED_FINGERPRINT_MIXER_SALT)); - } - - @Test - public void call_existingChunkListing_performsIncrementalBackup() throws Exception { - when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)) - .thenReturn(Optional.of(mOldChunkListing)); - mTask.call(); - verify(mEncryptedBackupTask) - .performIncrementalBackup( - eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(mOldChunkListing)); - } - - @Test - public void - call_existingChunkListingWithNoFingerprintMixerSalt_doesntSetSaltBeforeIncBackup() - throws Exception { - mOldChunkListing.fingerprintMixerSalt = new byte[0]; - when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)) - .thenReturn(Optional.of(mOldChunkListing)); - - mTask.call(); - - verify(mEncryptedBackupTask) - .performIncrementalBackup( - eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(mOldChunkListing)); - } - - @Test - public void call_noExistingChunkListing_storesNewChunkListing() throws Exception { - when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty()); - mTask.call(); - verify(mChunkListingStore).saveProto(TEST_PACKAGE_NAME, mNewChunkListing); - } - - @Test - public void call_existingChunkListing_storesNewChunkListing() throws Exception { - when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)) - .thenReturn(Optional.of(mOldChunkListing)); - mTask.call(); - verify(mChunkListingStore).saveProto(TEST_PACKAGE_NAME, mNewChunkListing); - } - - @Test - public void call_exceptionDuringBackup_doesNotSaveNewChunkListing() throws Exception { - when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)).thenReturn(Optional.empty()); - when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any())) - .thenThrow(GeneralSecurityException.class); - - assertThrows(Exception.class, () -> mTask.call()); - - assertThat(mChunkListingStore.loadProto(TEST_PACKAGE_NAME).isPresent()).isFalse(); - } - - @Test - public void call_incrementalThrowsPermanentException_clearsState() throws Exception { - when(mChunkListingStore.loadProto(TEST_PACKAGE_NAME)) - .thenReturn(Optional.of(mOldChunkListing)); - when(mEncryptedBackupTask.performIncrementalBackup(any(), any(), any())) - .thenThrow(IOException.class); - - assertThrows(IOException.class, () -> mTask.call()); - - verify(mChunkListingStore).deleteProto(TEST_PACKAGE_NAME); - } - - @Test - public void call_closesInputStream() throws Exception { - mTask.call(); - verify(mInputStream).close(); - } - - @Test - public void cancel_cancelsTask() throws Exception { - mTask.cancel(); - verify(mEncryptedBackupTask).cancel(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTaskTest.java deleted file mode 100644 index 0affacd114bf..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedFullRestoreTaskTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; - -import static java.util.stream.Collectors.toList; - -import com.android.server.backup.encryption.FullRestoreDownloader; - -import com.google.common.io.Files; -import com.google.common.primitives.Bytes; - -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 org.robolectric.RobolectricTestRunner; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; - -@RunWith(RobolectricTestRunner.class) -public class EncryptedFullRestoreTaskTest { - private static final int TEST_BUFFER_SIZE = 10; - private static final byte[] TEST_ENCRYPTED_DATA = {1, 2, 3, 4, 5, 6}; - private static final byte[] TEST_DECRYPTED_DATA = fakeDecrypt(TEST_ENCRYPTED_DATA); - - @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Mock private BackupFileDecryptorTask mDecryptorTask; - - private File mFolder; - private FakeFullRestoreDownloader mFullRestorePackageWrapper; - private EncryptedFullRestoreTask mTask; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mFolder = temporaryFolder.newFolder(); - mFullRestorePackageWrapper = new FakeFullRestoreDownloader(TEST_ENCRYPTED_DATA); - - doAnswer( - invocation -> { - File source = invocation.getArgument(0); - DecryptedChunkOutput target = invocation.getArgument(1); - byte[] decrypted = fakeDecrypt(Files.toByteArray(source)); - target.open(); - target.processChunk(decrypted, decrypted.length); - target.close(); - return null; - }) - .when(mDecryptorTask) - .decryptFile(any(), any()); - - mTask = new EncryptedFullRestoreTask(mFolder, mFullRestorePackageWrapper, mDecryptorTask); - } - - @Test - public void readNextChunk_downloadsAndDecryptsBackup() throws Exception { - ByteArrayOutputStream decryptedOutput = new ByteArrayOutputStream(); - - byte[] buffer = new byte[TEST_BUFFER_SIZE]; - int bytesRead = mTask.readNextChunk(buffer); - while (bytesRead != -1) { - decryptedOutput.write(buffer, 0, bytesRead); - bytesRead = mTask.readNextChunk(buffer); - } - - assertThat(decryptedOutput.toByteArray()).isEqualTo(TEST_DECRYPTED_DATA); - } - - @Test - public void finish_deletesTemporaryFiles() throws Exception { - mTask.readNextChunk(new byte[10]); - mTask.finish(FullRestoreDownloader.FinishType.UNKNOWN_FINISH); - - assertThat(mFolder.listFiles()).isEmpty(); - } - - /** Fake package wrapper which returns data from a byte array. */ - private static class FakeFullRestoreDownloader extends FullRestoreDownloader { - private final ByteArrayInputStream mData; - - FakeFullRestoreDownloader(byte[] data) { - // We override all methods of the superclass, so it does not require any collaborators. - super(); - mData = new ByteArrayInputStream(data); - } - - @Override - public int readNextChunk(byte[] buffer) throws IOException { - return mData.read(buffer); - } - - @Override - public void finish(FinishType finishType) { - // Nothing to do. - } - } - - /** Fake decrypts a byte array by subtracting 1 from each byte. */ - private static byte[] fakeDecrypt(byte[] input) { - return Bytes.toArray(Bytes.asList(input).stream().map(b -> b + 1).collect(toList())); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java deleted file mode 100644 index 222b88221ba2..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvBackupTaskTest.java +++ /dev/null @@ -1,357 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertThrows; - -import android.app.Application; -import android.util.Pair; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.ProtoStore; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; -import com.android.server.backup.encryption.keys.TertiaryKeyManager; -import com.android.server.backup.encryption.kv.KeyValueListingBuilder; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.protos.nano.KeyValueListingProto; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; -import com.android.server.backup.testing.CryptoTestUtils; - -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.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Map; -import java.util.Map.Entry; - -import javax.crypto.SecretKey; - - -@RunWith(RobolectricTestRunner.class) -public class EncryptedKvBackupTaskTest { - private static final boolean INCREMENTAL = true; - private static final boolean NON_INCREMENTAL = false; - - private static final String TEST_PACKAGE_1 = "com.example.app1"; - private static final String TEST_KEY_1 = "key_1"; - private static final String TEST_KEY_2 = "key_2"; - private static final ChunkHash TEST_HASH_1 = - new ChunkHash(Arrays.copyOf(new byte[] {1}, ChunkHash.HASH_LENGTH_BYTES)); - private static final ChunkHash TEST_HASH_2 = - new ChunkHash(Arrays.copyOf(new byte[] {2}, ChunkHash.HASH_LENGTH_BYTES)); - private static final int TEST_LENGTH_1 = 200; - private static final int TEST_LENGTH_2 = 300; - - @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - - @Captor private ArgumentCaptor<ChunksMetadataProto.ChunkListing> mChunkListingCaptor; - - @Mock private TertiaryKeyManager mTertiaryKeyManager; - @Mock private RecoverableKeyStoreSecondaryKey mSecondaryKey; - @Mock private ProtoStore<KeyValueListingProto.KeyValueListing> mKeyValueListingStore; - @Mock private ProtoStore<ChunksMetadataProto.ChunkListing> mChunkListingStore; - @Mock private KvBackupEncrypter mKvBackupEncrypter; - @Mock private EncryptedBackupTask mEncryptedBackupTask; - @Mock private SecretKey mTertiaryKey; - - private WrappedKeyProto.WrappedKey mWrappedTertiaryKey; - private KeyValueListingProto.KeyValueListing mNewKeyValueListing; - private ChunksMetadataProto.ChunkListing mNewChunkListing; - private EncryptedKvBackupTask mTask; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - Application application = ApplicationProvider.getApplicationContext(); - mKeyValueListingStore = ProtoStore.createKeyValueListingStore(application); - mChunkListingStore = ProtoStore.createChunkListingStore(application); - - mWrappedTertiaryKey = new WrappedKeyProto.WrappedKey(); - - when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(false); - when(mTertiaryKeyManager.getKey()).thenReturn(mTertiaryKey); - when(mTertiaryKeyManager.getWrappedKey()).thenReturn(mWrappedTertiaryKey); - - mNewKeyValueListing = - createKeyValueListing( - CryptoTestUtils.mapOf( - new Pair<>(TEST_KEY_1, TEST_HASH_1), - new Pair<>(TEST_KEY_2, TEST_HASH_2))); - mNewChunkListing = - createChunkListing( - CryptoTestUtils.mapOf( - new Pair<>(TEST_HASH_1, TEST_LENGTH_1), - new Pair<>(TEST_HASH_2, TEST_LENGTH_2))); - when(mKvBackupEncrypter.getNewKeyValueListing()).thenReturn(mNewKeyValueListing); - when(mEncryptedBackupTask.performIncrementalBackup( - eq(mTertiaryKey), eq(mWrappedTertiaryKey), any())) - .thenReturn(mNewChunkListing); - when(mEncryptedBackupTask.performNonIncrementalBackup( - eq(mTertiaryKey), eq(mWrappedTertiaryKey), any())) - .thenReturn(mNewChunkListing); - - mTask = - new EncryptedKvBackupTask( - mTertiaryKeyManager, - mKeyValueListingStore, - mSecondaryKey, - mChunkListingStore, - mKvBackupEncrypter, - mEncryptedBackupTask, - TEST_PACKAGE_1); - } - - @Test - public void testPerformBackup_rotationRequired_deletesListings() throws Exception { - mKeyValueListingStore.saveProto( - TEST_PACKAGE_1, - createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1)))); - mChunkListingStore.saveProto( - TEST_PACKAGE_1, - createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1)))); - - when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true); - // Throw an IOException so it aborts before saving the new listings. - when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any())) - .thenThrow(IOException.class); - - assertThrows(IOException.class, () -> mTask.performBackup(NON_INCREMENTAL)); - - assertFalse(mKeyValueListingStore.loadProto(TEST_PACKAGE_1).isPresent()); - assertFalse(mChunkListingStore.loadProto(TEST_PACKAGE_1).isPresent()); - } - - @Test - public void testPerformBackup_rotationRequiredButIncremental_throws() throws Exception { - mKeyValueListingStore.saveProto( - TEST_PACKAGE_1, - createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1)))); - mChunkListingStore.saveProto( - TEST_PACKAGE_1, - createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1)))); - - when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true); - - assertThrows(NonIncrementalBackupRequiredException.class, - () -> mTask.performBackup(INCREMENTAL)); - } - - @Test - public void testPerformBackup_rotationRequiredAndNonIncremental_performsNonIncrementalBackup() - throws Exception { - mKeyValueListingStore.saveProto( - TEST_PACKAGE_1, - createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1)))); - mChunkListingStore.saveProto( - TEST_PACKAGE_1, - createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1)))); - - when(mTertiaryKeyManager.wasKeyRotated()).thenReturn(true); - - mTask.performBackup(NON_INCREMENTAL); - - verify(mEncryptedBackupTask) - .performNonIncrementalBackup(eq(mTertiaryKey), eq(mWrappedTertiaryKey), any()); - } - - @Test - public void testPerformBackup_existingStateButNonIncremental_deletesListings() throws Exception { - mKeyValueListingStore.saveProto( - TEST_PACKAGE_1, - createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1)))); - mChunkListingStore.saveProto( - TEST_PACKAGE_1, - createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1)))); - - // Throw an IOException so it aborts before saving the new listings. - when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any())) - .thenThrow(IOException.class); - - assertThrows(IOException.class, () -> mTask.performBackup(NON_INCREMENTAL)); - - assertFalse(mKeyValueListingStore.loadProto(TEST_PACKAGE_1).isPresent()); - assertFalse(mChunkListingStore.loadProto(TEST_PACKAGE_1).isPresent()); - } - - @Test - public void testPerformBackup_keyValueListingMissing_deletesChunkListingAndPerformsNonIncremental() - throws Exception { - mChunkListingStore.saveProto( - TEST_PACKAGE_1, - createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1)))); - - // Throw an IOException so it aborts before saving the new listings. - when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any())) - .thenThrow(IOException.class); - - assertThrows(IOException.class, () -> mTask.performBackup(NON_INCREMENTAL)); - - verify(mEncryptedBackupTask).performNonIncrementalBackup(any(), any(), any()); - assertFalse(mKeyValueListingStore.loadProto(TEST_PACKAGE_1).isPresent()); - assertFalse(mChunkListingStore.loadProto(TEST_PACKAGE_1).isPresent()); - } - - @Test - public void testPerformBackup_chunkListingMissing_deletesKeyValueListingAndPerformsNonIncremental() - throws Exception { - mKeyValueListingStore.saveProto( - TEST_PACKAGE_1, - createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1)))); - - // Throw an IOException so it aborts before saving the new listings. - when(mEncryptedBackupTask.performNonIncrementalBackup(any(), any(), any())) - .thenThrow(IOException.class); - - assertThrows(IOException.class, () -> mTask.performBackup(NON_INCREMENTAL)); - - verify(mEncryptedBackupTask).performNonIncrementalBackup(any(), any(), any()); - assertFalse(mKeyValueListingStore.loadProto(TEST_PACKAGE_1).isPresent()); - assertFalse(mChunkListingStore.loadProto(TEST_PACKAGE_1).isPresent()); - } - - @Test - public void testPerformBackup_existingStateAndIncremental_performsIncrementalBackup() - throws Exception { - mKeyValueListingStore.saveProto( - TEST_PACKAGE_1, - createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1)))); - ChunksMetadataProto.ChunkListing oldChunkListing = - createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1))); - mChunkListingStore.saveProto(TEST_PACKAGE_1, oldChunkListing); - - mTask.performBackup(INCREMENTAL); - - verify(mEncryptedBackupTask) - .performIncrementalBackup( - eq(mTertiaryKey), eq(mWrappedTertiaryKey), mChunkListingCaptor.capture()); - assertChunkListingsEqual(mChunkListingCaptor.getValue(), oldChunkListing); - } - - @Test - public void testPerformBackup_noExistingStateAndNonIncremental_performsNonIncrementalBackup() - throws Exception { - mTask.performBackup(NON_INCREMENTAL); - - verify(mEncryptedBackupTask) - .performNonIncrementalBackup(eq(mTertiaryKey), eq(mWrappedTertiaryKey), eq(null)); - } - - @Test - public void testPerformBackup_incremental_savesNewListings() throws Exception { - mKeyValueListingStore.saveProto( - TEST_PACKAGE_1, - createKeyValueListing(CryptoTestUtils.mapOf(new Pair<>(TEST_KEY_1, TEST_HASH_1)))); - mChunkListingStore.saveProto( - TEST_PACKAGE_1, - createChunkListing(CryptoTestUtils.mapOf(new Pair<>(TEST_HASH_1, TEST_LENGTH_1)))); - - mTask.performBackup(INCREMENTAL); - - KeyValueListingProto.KeyValueListing actualKeyValueListing = - mKeyValueListingStore.loadProto(TEST_PACKAGE_1).get(); - ChunksMetadataProto.ChunkListing actualChunkListing = - mChunkListingStore.loadProto(TEST_PACKAGE_1).get(); - assertKeyValueListingsEqual(actualKeyValueListing, mNewKeyValueListing); - assertChunkListingsEqual(actualChunkListing, mNewChunkListing); - } - - @Test - public void testPerformBackup_nonIncremental_savesNewListings() throws Exception { - mTask.performBackup(NON_INCREMENTAL); - - KeyValueListingProto.KeyValueListing actualKeyValueListing = - mKeyValueListingStore.loadProto(TEST_PACKAGE_1).get(); - ChunksMetadataProto.ChunkListing actualChunkListing = - mChunkListingStore.loadProto(TEST_PACKAGE_1).get(); - assertKeyValueListingsEqual(actualKeyValueListing, mNewKeyValueListing); - assertChunkListingsEqual(actualChunkListing, mNewChunkListing); - } - - private static KeyValueListingProto.KeyValueListing createKeyValueListing( - Map<String, ChunkHash> pairs) { - return new KeyValueListingBuilder().addAll(pairs).build(); - } - - private static ChunksMetadataProto.ChunkListing createChunkListing( - Map<ChunkHash, Integer> chunks) { - ChunksMetadataProto.Chunk[] listingChunks = new ChunksMetadataProto.Chunk[chunks.size()]; - int chunksAdded = 0; - for (Entry<ChunkHash, Integer> entry : chunks.entrySet()) { - listingChunks[chunksAdded] = CryptoTestUtils.newChunk(entry.getKey(), entry.getValue()); - chunksAdded++; - } - return CryptoTestUtils.newChunkListingWithoutDocId( - /* fingerprintSalt */ new byte[0], - ChunksMetadataProto.AES_256_GCM, - ChunksMetadataProto.CHUNK_ORDERING_TYPE_UNSPECIFIED, - listingChunks); - } - - private static void assertKeyValueListingsEqual( - KeyValueListingProto.KeyValueListing actual, - KeyValueListingProto.KeyValueListing expected) { - KeyValueListingProto.KeyValueEntry[] actualEntries = actual.entries; - KeyValueListingProto.KeyValueEntry[] expectedEntries = expected.entries; - assertThat(actualEntries.length).isEqualTo(expectedEntries.length); - for (int i = 0; i < actualEntries.length; i++) { - assertWithMessage("entry " + i) - .that(actualEntries[i].key) - .isEqualTo(expectedEntries[i].key); - assertWithMessage("entry " + i) - .that(actualEntries[i].hash) - .isEqualTo(expectedEntries[i].hash); - } - } - - private static void assertChunkListingsEqual( - ChunksMetadataProto.ChunkListing actual, ChunksMetadataProto.ChunkListing expected) { - ChunksMetadataProto.Chunk[] actualChunks = actual.chunks; - ChunksMetadataProto.Chunk[] expectedChunks = expected.chunks; - assertThat(actualChunks.length).isEqualTo(expectedChunks.length); - for (int i = 0; i < actualChunks.length; i++) { - assertWithMessage("chunk " + i) - .that(actualChunks[i].hash) - .isEqualTo(expectedChunks[i].hash); - assertWithMessage("chunk " + i) - .that(actualChunks[i].length) - .isEqualTo(expectedChunks[i].length); - } - assertThat(actual.cipherType).isEqualTo(expected.cipherType); - assertThat(actual.documentId) - .isEqualTo(expected.documentId == null ? "" : expected.documentId); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvRestoreTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvRestoreTaskTest.java deleted file mode 100644 index 6666d95d9a2d..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/EncryptedKvRestoreTaskTest.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; - -import android.os.ParcelFileDescriptor; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.ChunkHasher; -import com.android.server.backup.testing.CryptoTestUtils; -import com.android.server.testing.shadows.DataEntity; -import com.android.server.testing.shadows.ShadowBackupDataOutput; - -import com.google.protobuf.nano.MessageNano; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -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 org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@Config(shadows = {ShadowBackupDataOutput.class}) -@RunWith(RobolectricTestRunner.class) -public class EncryptedKvRestoreTaskTest { - private static final String TEST_KEY_1 = "test_key_1"; - private static final String TEST_KEY_2 = "test_key_2"; - private static final String TEST_KEY_3 = "test_key_3"; - private static final byte[] TEST_VALUE_1 = {1, 2, 3}; - private static final byte[] TEST_VALUE_2 = {4, 5, 6}; - private static final byte[] TEST_VALUE_3 = {20, 25, 30, 35}; - - @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - private File temporaryDirectory; - - @Mock private ParcelFileDescriptor mParcelFileDescriptor; - @Mock private ChunkHasher mChunkHasher; - @Mock private FullRestoreToFileTask mFullRestoreToFileTask; - @Mock private BackupFileDecryptorTask mBackupFileDecryptorTask; - - private EncryptedKvRestoreTask task; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - when(mChunkHasher.computeHash(any())) - .thenAnswer(invocation -> fakeHash(invocation.getArgument(0))); - doAnswer(invocation -> writeTestPairsToFile(invocation.getArgument(0))) - .when(mFullRestoreToFileTask) - .restoreToFile(any()); - doAnswer( - invocation -> - readPairsFromFile( - invocation.getArgument(0), invocation.getArgument(1))) - .when(mBackupFileDecryptorTask) - .decryptFile(any(), any()); - - temporaryDirectory = temporaryFolder.newFolder(); - task = - new EncryptedKvRestoreTask( - temporaryDirectory, - mChunkHasher, - mFullRestoreToFileTask, - mBackupFileDecryptorTask); - } - - @Test - public void testGetRestoreData_writesPairsToOutputInOrder() throws Exception { - task.getRestoreData(mParcelFileDescriptor); - - assertThat(ShadowBackupDataOutput.getEntities()) - .containsExactly( - new DataEntity(TEST_KEY_1, TEST_VALUE_1), - new DataEntity(TEST_KEY_2, TEST_VALUE_2), - new DataEntity(TEST_KEY_3, TEST_VALUE_3)) - .inOrder(); - } - - @Test - public void testGetRestoreData_exceptionDuringDecryption_throws() throws Exception { - doThrow(IOException.class).when(mBackupFileDecryptorTask).decryptFile(any(), any()); - assertThrows(IOException.class, () -> task.getRestoreData(mParcelFileDescriptor)); - } - - @Test - public void testGetRestoreData_exceptionDuringDownload_throws() throws Exception { - doThrow(IOException.class).when(mFullRestoreToFileTask).restoreToFile(any()); - assertThrows(IOException.class, () -> task.getRestoreData(mParcelFileDescriptor)); - } - - @Test - public void testGetRestoreData_exceptionDuringDecryption_deletesTemporaryFiles() throws Exception { - doThrow(InvalidKeyException.class).when(mBackupFileDecryptorTask).decryptFile(any(), any()); - assertThrows(InvalidKeyException.class, () -> task.getRestoreData(mParcelFileDescriptor)); - assertThat(temporaryDirectory.listFiles()).isEmpty(); - } - - @Test - public void testGetRestoreData_exceptionDuringDownload_deletesTemporaryFiles() throws Exception { - doThrow(IOException.class).when(mFullRestoreToFileTask).restoreToFile(any()); - assertThrows(IOException.class, () -> task.getRestoreData(mParcelFileDescriptor)); - assertThat(temporaryDirectory.listFiles()).isEmpty(); - } - - private static Void writeTestPairsToFile(File file) throws IOException { - // Write the pairs out of order to check the task sorts them. - Set<byte[]> pairs = - new HashSet<>( - Arrays.asList( - createPair(TEST_KEY_1, TEST_VALUE_1), - createPair(TEST_KEY_3, TEST_VALUE_3), - createPair(TEST_KEY_2, TEST_VALUE_2))); - - try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file))) { - oos.writeObject(pairs); - } - return null; - } - - private static Void readPairsFromFile(File file, DecryptedChunkOutput decryptedChunkOutput) - throws IOException, ClassNotFoundException, InvalidKeyException, - NoSuchAlgorithmException { - try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); - DecryptedChunkOutput output = decryptedChunkOutput.open()) { - Set<byte[]> pairs = readPairs(ois); - for (byte[] pair : pairs) { - output.processChunk(pair, pair.length); - } - } - - return null; - } - - private static byte[] createPair(String key, byte[] value) { - return MessageNano.toByteArray(CryptoTestUtils.newPair(key, value)); - } - - @SuppressWarnings("unchecked") // deserialization. - private static Set<byte[]> readPairs(ObjectInputStream ois) - throws IOException, ClassNotFoundException { - return (Set<byte[]>) ois.readObject(); - } - - private static ChunkHash fakeHash(byte[] data) { - return new ChunkHash(Arrays.copyOf(data, ChunkHash.HASH_LENGTH_BYTES)); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java deleted file mode 100644 index de8b7340ebce..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/FullRestoreToFileTaskTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; - -import android.platform.test.annotations.Presubmit; - -import com.android.server.backup.encryption.FullRestoreDownloader; -import com.android.server.backup.encryption.FullRestoreDownloader.FinishType; - -import com.google.common.io.Files; - -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 org.robolectric.RobolectricTestRunner; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.util.Random; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -public class FullRestoreToFileTaskTest { - private static final int TEST_RANDOM_SEED = 34; - private static final int TEST_MAX_CHUNK_SIZE_BYTES = 5; - private static final int TEST_DATA_LENGTH_BYTES = TEST_MAX_CHUNK_SIZE_BYTES * 20; - - @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); - - private byte[] mTestData; - private File mTargetFile; - private FakeFullRestoreDownloader mFakeFullRestoreDownloader; - @Mock private FullRestoreDownloader mMockFullRestoreDownloader; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - - mTargetFile = mTemporaryFolder.newFile(); - - mTestData = new byte[TEST_DATA_LENGTH_BYTES]; - new Random(TEST_RANDOM_SEED).nextBytes(mTestData); - mFakeFullRestoreDownloader = new FakeFullRestoreDownloader(mTestData); - } - - private FullRestoreToFileTask createTaskWithFakeDownloader() { - return new FullRestoreToFileTask(mFakeFullRestoreDownloader, TEST_MAX_CHUNK_SIZE_BYTES); - } - - private FullRestoreToFileTask createTaskWithMockDownloader() { - return new FullRestoreToFileTask(mMockFullRestoreDownloader, TEST_MAX_CHUNK_SIZE_BYTES); - } - - @Test - public void restoreToFile_readsDataAndWritesToFile() throws Exception { - FullRestoreToFileTask task = createTaskWithFakeDownloader(); - task.restoreToFile(mTargetFile); - assertThat(Files.toByteArray(mTargetFile)).isEqualTo(mTestData); - } - - @Test - public void restoreToFile_noErrors_closesDownloaderWithFinished() throws Exception { - FullRestoreToFileTask task = createTaskWithMockDownloader(); - when(mMockFullRestoreDownloader.readNextChunk(any())).thenReturn(-1); - - task.restoreToFile(mTargetFile); - - verify(mMockFullRestoreDownloader).finish(FinishType.FINISHED); - } - - @Test - public void restoreToFile_ioException_closesDownloaderWithTransferFailure() throws Exception { - FullRestoreToFileTask task = createTaskWithMockDownloader(); - when(mMockFullRestoreDownloader.readNextChunk(any())).thenThrow(IOException.class); - - assertThrows(IOException.class, () -> task.restoreToFile(mTargetFile)); - - verify(mMockFullRestoreDownloader).finish(FinishType.TRANSFER_FAILURE); - } - - /** Fake package wrapper which returns data from a byte array. */ - private static class FakeFullRestoreDownloader extends FullRestoreDownloader { - - private final ByteArrayInputStream mData; - - FakeFullRestoreDownloader(byte[] data) { - // We override all methods of the superclass, so it does not require any collaborators. - super(); - this.mData = new ByteArrayInputStream(data); - } - - @Override - public int readNextChunk(byte[] buffer) throws IOException { - return mData.read(buffer); - } - - @Override - public void finish(FinishType finishType) { - // Do nothing. - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTaskTest.java deleted file mode 100644 index 4a7ae03bd0db..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/InitializeRecoverableSecondaryKeyTaskTest.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * 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.security.keystore.recovery.RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertThrows; - -import android.app.Application; -import android.security.keystore.recovery.RecoveryController; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.server.backup.encryption.CryptoSettings; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; -import com.android.server.testing.fakes.FakeCryptoBackupServer; -import com.android.server.testing.shadows.ShadowRecoveryController; - -import java.security.InvalidKeyException; -import java.security.SecureRandom; -import java.util.Optional; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@Config(shadows = {ShadowRecoveryController.class}) -@RunWith(RobolectricTestRunner.class) -public class InitializeRecoverableSecondaryKeyTaskTest { - @Mock private CryptoSettings mMockCryptoSettings; - - private Application mApplication; - private InitializeRecoverableSecondaryKeyTask mTask; - private CryptoSettings mCryptoSettings; - private FakeCryptoBackupServer mFakeCryptoBackupServer; - private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - ShadowRecoveryController.reset(); - - mApplication = ApplicationProvider.getApplicationContext(); - mFakeCryptoBackupServer = new FakeCryptoBackupServer(); - mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication); - mSecondaryKeyManager = - new RecoverableKeyStoreSecondaryKeyManager( - RecoveryController.getInstance(mApplication), new SecureRandom()); - - mTask = - new InitializeRecoverableSecondaryKeyTask( - mApplication, mCryptoSettings, mSecondaryKeyManager, mFakeCryptoBackupServer); - } - - @Test - public void testRun_generatesNewKeyInRecoveryController() throws Exception { - RecoverableKeyStoreSecondaryKey key = mTask.run(); - - assertThat(RecoveryController.getInstance(mApplication).getAliases()) - .contains(key.getAlias()); - } - - @Test - public void testRun_setsAliasOnServer() throws Exception { - RecoverableKeyStoreSecondaryKey key = mTask.run(); - - assertThat(mFakeCryptoBackupServer.getActiveSecondaryKeyAlias().get()) - .isEqualTo(key.getAlias()); - } - - @Test - public void testRun_setsAliasInSettings() throws Exception { - RecoverableKeyStoreSecondaryKey key = mTask.run(); - - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()).isEqualTo(key.getAlias()); - } - - @Test - public void testRun_initializesSettings() throws Exception { - mTask.run(); - - assertThat(mCryptoSettings.getIsInitialized()).isTrue(); - } - - @Test - public void testRun_initializeSettingsFails_throws() throws Exception { - useMockCryptoSettings(); - doThrow(IllegalArgumentException.class) - .when(mMockCryptoSettings) - .initializeWithKeyAlias(any()); - - - assertThrows(IllegalArgumentException.class, () -> mTask.run()); - } - - @Test - public void testRun_doesNotGenerateANewKeyIfOneIsAvailable() throws Exception { - RecoverableKeyStoreSecondaryKey key1 = mTask.run(); - RecoverableKeyStoreSecondaryKey key2 = mTask.run(); - - assertThat(key1.getAlias()).isEqualTo(key2.getAlias()); - assertThat(key2.getSecretKey()).isEqualTo(key2.getSecretKey()); - } - - @Test - public void testRun_existingKeyButDestroyed_throws() throws Exception { - RecoverableKeyStoreSecondaryKey key = mTask.run(); - ShadowRecoveryController.setRecoveryStatus( - key.getAlias(), RECOVERY_STATUS_PERMANENT_FAILURE); - - assertThrows(InvalidKeyException.class, () -> mTask.run()); - } - - @Test - public void testRun_settingsInitializedButNotSecondaryKeyAlias_throws() { - useMockCryptoSettings(); - when(mMockCryptoSettings.getIsInitialized()).thenReturn(true); - when(mMockCryptoSettings.getActiveSecondaryKeyAlias()).thenReturn(Optional.empty()); - - assertThrows(InvalidKeyException.class, () -> mTask.run()); - } - - @Test - public void testRun_keyAliasSetButNotInStore_throws() { - useMockCryptoSettings(); - when(mMockCryptoSettings.getIsInitialized()).thenReturn(true); - when(mMockCryptoSettings.getActiveSecondaryKeyAlias()) - .thenReturn(Optional.of("missingAlias")); - - assertThrows(InvalidKeyException.class, () -> mTask.run()); - } - - private void useMockCryptoSettings() { - mTask = - new InitializeRecoverableSecondaryKeyTask( - mApplication, - mMockCryptoSettings, - mSecondaryKeyManager, - mFakeCryptoBackupServer); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/KvBackupEncrypterTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/KvBackupEncrypterTest.java deleted file mode 100644 index ccfbfa4b25e9..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/KvBackupEncrypterTest.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertThrows; - -import android.app.backup.BackupDataInput; -import android.platform.test.annotations.Presubmit; -import android.util.Pair; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.chunking.ChunkHasher; -import com.android.server.backup.encryption.chunking.EncryptedChunk; -import com.android.server.backup.encryption.kv.KeyValueListingBuilder; -import com.android.server.backup.encryption.protos.nano.KeyValueListingProto.KeyValueListing; -import com.android.server.backup.encryption.protos.nano.KeyValuePairProto.KeyValuePair; -import com.android.server.backup.encryption.tasks.BackupEncrypter.Result; -import com.android.server.testing.shadows.DataEntity; -import com.android.server.testing.shadows.ShadowBackupDataInput; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Ordering; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -import java.security.MessageDigest; -import java.util.Arrays; -import java.util.Set; -import java.util.stream.Collectors; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -@Config(shadows = {ShadowBackupDataInput.class}) -public class KvBackupEncrypterTest { - private static final String KEY_ALGORITHM = "AES"; - private static final String CIPHER_ALGORITHM = "AES/GCM/NoPadding"; - private static final int GCM_TAG_LENGTH_BYTES = 16; - - private static final byte[] TEST_TERTIARY_KEY = Arrays.copyOf(new byte[0], 256 / Byte.SIZE); - private static final String TEST_KEY_1 = "test_key_1"; - private static final String TEST_KEY_2 = "test_key_2"; - private static final String TEST_KEY_3 = "test_key_3"; - private static final byte[] TEST_VALUE_1 = {10, 11, 12}; - private static final byte[] TEST_VALUE_2 = {13, 14, 15}; - private static final byte[] TEST_VALUE_2B = {13, 14, 15, 16}; - private static final byte[] TEST_VALUE_3 = {16, 17, 18}; - - private SecretKey mSecretKey; - private ChunkHasher mChunkHasher; - - @Before - public void setUp() { - mSecretKey = new SecretKeySpec(TEST_TERTIARY_KEY, KEY_ALGORITHM); - mChunkHasher = new ChunkHasher(mSecretKey); - - ShadowBackupDataInput.reset(); - } - - private KvBackupEncrypter createEncrypter(KeyValueListing keyValueListing) { - KvBackupEncrypter encrypter = new KvBackupEncrypter(new BackupDataInput(null)); - encrypter.setOldKeyValueListing(keyValueListing); - return encrypter; - } - - @Test - public void backup_noExistingBackup_encryptsAllPairs() throws Exception { - ShadowBackupDataInput.addEntity(TEST_KEY_1, TEST_VALUE_1); - ShadowBackupDataInput.addEntity(TEST_KEY_2, TEST_VALUE_2); - - KeyValueListing emptyKeyValueListing = new KeyValueListingBuilder().build(); - ImmutableSet<ChunkHash> emptyExistingChunks = ImmutableSet.of(); - KvBackupEncrypter encrypter = createEncrypter(emptyKeyValueListing); - - Result result = - encrypter.backup( - mSecretKey, /*unusedFingerprintMixerSalt=*/ null, emptyExistingChunks); - - assertThat(result.getAllChunks()).hasSize(2); - EncryptedChunk chunk1 = result.getNewChunks().get(0); - EncryptedChunk chunk2 = result.getNewChunks().get(1); - assertThat(chunk1.key()).isEqualTo(getChunkHash(TEST_KEY_1, TEST_VALUE_1)); - KeyValuePair pair1 = decryptChunk(chunk1); - assertThat(pair1.key).isEqualTo(TEST_KEY_1); - assertThat(pair1.value).isEqualTo(TEST_VALUE_1); - assertThat(chunk2.key()).isEqualTo(getChunkHash(TEST_KEY_2, TEST_VALUE_2)); - KeyValuePair pair2 = decryptChunk(chunk2); - assertThat(pair2.key).isEqualTo(TEST_KEY_2); - assertThat(pair2.value).isEqualTo(TEST_VALUE_2); - } - - @Test - public void backup_existingBackup_encryptsNewAndUpdatedPairs() throws Exception { - Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2(); - - // Update key 2 and add the new key 3. - ShadowBackupDataInput.reset(); - ShadowBackupDataInput.addEntity(TEST_KEY_2, TEST_VALUE_2B); - ShadowBackupDataInput.addEntity(TEST_KEY_3, TEST_VALUE_3); - - KvBackupEncrypter encrypter = createEncrypter(initialResult.first); - BackupEncrypter.Result secondResult = - encrypter.backup( - mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second); - - assertThat(secondResult.getAllChunks()).hasSize(3); - assertThat(secondResult.getNewChunks()).hasSize(2); - EncryptedChunk newChunk2 = secondResult.getNewChunks().get(0); - EncryptedChunk newChunk3 = secondResult.getNewChunks().get(1); - assertThat(newChunk2.key()).isEqualTo(getChunkHash(TEST_KEY_2, TEST_VALUE_2B)); - assertThat(decryptChunk(newChunk2).value).isEqualTo(TEST_VALUE_2B); - assertThat(newChunk3.key()).isEqualTo(getChunkHash(TEST_KEY_3, TEST_VALUE_3)); - assertThat(decryptChunk(newChunk3).value).isEqualTo(TEST_VALUE_3); - } - - @Test - public void backup_allChunksContainsHashesOfAllChunks() throws Exception { - Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2(); - - ShadowBackupDataInput.reset(); - ShadowBackupDataInput.addEntity(TEST_KEY_3, TEST_VALUE_3); - - KvBackupEncrypter encrypter = createEncrypter(initialResult.first); - BackupEncrypter.Result secondResult = - encrypter.backup( - mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second); - - assertThat(secondResult.getAllChunks()) - .containsExactly( - getChunkHash(TEST_KEY_1, TEST_VALUE_1), - getChunkHash(TEST_KEY_2, TEST_VALUE_2), - getChunkHash(TEST_KEY_3, TEST_VALUE_3)); - } - - @Test - public void backup_negativeSize_deletesKeyFromExistingBackup() throws Exception { - Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2(); - - ShadowBackupDataInput.reset(); - ShadowBackupDataInput.addEntity(new DataEntity(TEST_KEY_2)); - - KvBackupEncrypter encrypter = createEncrypter(initialResult.first); - Result secondResult = - encrypter.backup( - mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second); - - assertThat(secondResult.getAllChunks()) - .containsExactly(getChunkHash(TEST_KEY_1, TEST_VALUE_1)); - assertThat(secondResult.getNewChunks()).isEmpty(); - } - - @Test - public void backup_returnsMessageDigestOverChunkHashes() throws Exception { - Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2(); - - ShadowBackupDataInput.reset(); - ShadowBackupDataInput.addEntity(TEST_KEY_3, TEST_VALUE_3); - - KvBackupEncrypter encrypter = createEncrypter(initialResult.first); - Result secondResult = - encrypter.backup( - mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second); - - MessageDigest messageDigest = - MessageDigest.getInstance(BackupEncrypter.MESSAGE_DIGEST_ALGORITHM); - ImmutableList<ChunkHash> sortedHashes = - Ordering.natural() - .immutableSortedCopy( - ImmutableList.of( - getChunkHash(TEST_KEY_1, TEST_VALUE_1), - getChunkHash(TEST_KEY_2, TEST_VALUE_2), - getChunkHash(TEST_KEY_3, TEST_VALUE_3))); - messageDigest.update(sortedHashes.get(0).getHash()); - messageDigest.update(sortedHashes.get(1).getHash()); - messageDigest.update(sortedHashes.get(2).getHash()); - assertThat(secondResult.getDigest()).isEqualTo(messageDigest.digest()); - } - - @Test - public void getNewKeyValueListing_noExistingBackup_returnsCorrectListing() throws Exception { - KeyValueListing keyValueListing = runInitialBackupOfPairs1And2().first; - - assertThat(keyValueListing.entries.length).isEqualTo(2); - assertThat(keyValueListing.entries[0].key).isEqualTo(TEST_KEY_1); - assertThat(keyValueListing.entries[0].hash) - .isEqualTo(getChunkHash(TEST_KEY_1, TEST_VALUE_1).getHash()); - assertThat(keyValueListing.entries[1].key).isEqualTo(TEST_KEY_2); - assertThat(keyValueListing.entries[1].hash) - .isEqualTo(getChunkHash(TEST_KEY_2, TEST_VALUE_2).getHash()); - } - - @Test - public void getNewKeyValueListing_existingBackup_returnsCorrectListing() throws Exception { - Pair<KeyValueListing, Set<ChunkHash>> initialResult = runInitialBackupOfPairs1And2(); - - ShadowBackupDataInput.reset(); - ShadowBackupDataInput.addEntity(TEST_KEY_2, TEST_VALUE_2B); - ShadowBackupDataInput.addEntity(TEST_KEY_3, TEST_VALUE_3); - - KvBackupEncrypter encrypter = createEncrypter(initialResult.first); - encrypter.backup(mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialResult.second); - - ImmutableMap<String, ChunkHash> keyValueListing = - listingToMap(encrypter.getNewKeyValueListing()); - assertThat(keyValueListing).hasSize(3); - assertThat(keyValueListing) - .containsEntry(TEST_KEY_1, getChunkHash(TEST_KEY_1, TEST_VALUE_1)); - assertThat(keyValueListing) - .containsEntry(TEST_KEY_2, getChunkHash(TEST_KEY_2, TEST_VALUE_2B)); - assertThat(keyValueListing) - .containsEntry(TEST_KEY_3, getChunkHash(TEST_KEY_3, TEST_VALUE_3)); - } - - @Test - public void getNewKeyValueChunkListing_beforeBackup_throws() throws Exception { - KvBackupEncrypter encrypter = createEncrypter(new KeyValueListing()); - assertThrows(IllegalStateException.class, encrypter::getNewKeyValueListing); - } - - private ImmutableMap<String, ChunkHash> listingToMap(KeyValueListing listing) { - // We can't use the ImmutableMap collector directly because it isn't supported in Android - // guava. - return ImmutableMap.copyOf( - Arrays.stream(listing.entries) - .collect( - Collectors.toMap( - entry -> entry.key, entry -> new ChunkHash(entry.hash)))); - } - - private Pair<KeyValueListing, Set<ChunkHash>> runInitialBackupOfPairs1And2() throws Exception { - ShadowBackupDataInput.addEntity(TEST_KEY_1, TEST_VALUE_1); - ShadowBackupDataInput.addEntity(TEST_KEY_2, TEST_VALUE_2); - - KeyValueListing initialKeyValueListing = new KeyValueListingBuilder().build(); - ImmutableSet<ChunkHash> initialExistingChunks = ImmutableSet.of(); - KvBackupEncrypter encrypter = createEncrypter(initialKeyValueListing); - Result firstResult = - encrypter.backup( - mSecretKey, /*unusedFingerprintMixerSalt=*/ null, initialExistingChunks); - - return Pair.create( - encrypter.getNewKeyValueListing(), ImmutableSet.copyOf(firstResult.getAllChunks())); - } - - private ChunkHash getChunkHash(String key, byte[] value) throws Exception { - KeyValuePair pair = new KeyValuePair(); - pair.key = key; - pair.value = Arrays.copyOf(value, value.length); - return mChunkHasher.computeHash(KeyValuePair.toByteArray(pair)); - } - - private KeyValuePair decryptChunk(EncryptedChunk encryptedChunk) throws Exception { - Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); - cipher.init( - Cipher.DECRYPT_MODE, - mSecretKey, - new GCMParameterSpec(GCM_TAG_LENGTH_BYTES * Byte.SIZE, encryptedChunk.nonce())); - byte[] decryptedBytes = cipher.doFinal(encryptedChunk.encryptedBytes()); - return KeyValuePair.parseFrom(decryptedBytes); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTaskTest.java deleted file mode 100644 index cda73174a3e2..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTaskTest.java +++ /dev/null @@ -1,363 +0,0 @@ -/* - * 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 com.android.server.backup.testing.CryptoTestUtils.generateAesKey; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertFalse; - -import android.app.Application; -import android.platform.test.annotations.Presubmit; -import android.security.keystore.recovery.RecoveryController; - -import androidx.test.core.app.ApplicationProvider; - -import com.android.server.backup.encryption.CryptoSettings; -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 com.android.server.testing.fakes.FakeCryptoBackupServer; -import com.android.server.testing.shadows.ShadowRecoveryController; - -import java.security.SecureRandom; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import javax.crypto.SecretKey; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.annotation.Config; - -@RunWith(RobolectricTestRunner.class) -@Presubmit -@Config(shadows = {ShadowRecoveryController.class, ShadowRecoveryController.class}) -public class RotateSecondaryKeyTaskTest { - private static final String APP_1 = "app1"; - private static final String APP_2 = "app2"; - private static final String APP_3 = "app3"; - - private static final String CURRENT_SECONDARY_KEY_ALIAS = - "recoverablekey.alias/d524796bd07de3c2225c63d434eff698"; - private static final String NEXT_SECONDARY_KEY_ALIAS = - "recoverablekey.alias/6c6d198a7f12e662b6bc45f4849db170"; - - private Application mApplication; - private RotateSecondaryKeyTask mTask; - private RecoveryController mRecoveryController; - private FakeCryptoBackupServer mBackupServer; - private CryptoSettings mCryptoSettings; - private Map<String, SecretKey> mTertiaryKeysByPackageName; - private RecoverableKeyStoreSecondaryKeyManager mRecoverableSecondaryKeyManager; - - @Before - public void setUp() throws Exception { - mApplication = ApplicationProvider.getApplicationContext(); - - mTertiaryKeysByPackageName = new HashMap<>(); - mTertiaryKeysByPackageName.put(APP_1, generateAesKey()); - mTertiaryKeysByPackageName.put(APP_2, generateAesKey()); - mTertiaryKeysByPackageName.put(APP_3, generateAesKey()); - - mRecoveryController = RecoveryController.getInstance(mApplication); - mRecoverableSecondaryKeyManager = - new RecoverableKeyStoreSecondaryKeyManager( - RecoveryController.getInstance(mApplication), new SecureRandom()); - mBackupServer = new FakeCryptoBackupServer(); - mCryptoSettings = CryptoSettings.getInstanceForTesting(mApplication); - addNextSecondaryKeyToRecoveryController(); - mCryptoSettings.setNextSecondaryAlias(NEXT_SECONDARY_KEY_ALIAS); - - mTask = - new RotateSecondaryKeyTask( - mApplication, - mRecoverableSecondaryKeyManager, - mBackupServer, - mCryptoSettings, - mRecoveryController); - - ShadowRecoveryController.reset(); - } - - @Test - public void run_failsIfThereIsNoActiveSecondaryKey() throws Exception { - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - addCurrentSecondaryKeyToRecoveryController(); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertFalse(mCryptoSettings.getActiveSecondaryKeyAlias().isPresent()); - } - - @Test - public void run_failsIfActiveSecondaryIsNotInRecoveryController() throws Exception { - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - // Have to add it first as otherwise CryptoSettings throws an exception when trying to set - // it - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()) - .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS); - } - - @Test - public void run_doesNothingIfFlagIsDisabled() throws Exception { - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - addWrappedTertiaries(); - - mTask.run(); - - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()) - .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS); - } - - @Test - public void run_setsActiveSecondary() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - addWrappedTertiaries(); - - mTask.run(); - - assertThat(mBackupServer.getActiveSecondaryKeyAlias().get()) - .isEqualTo(NEXT_SECONDARY_KEY_ALIAS); - } - - @Test - public void run_rewrapsExistingTertiaryKeys() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - addWrappedTertiaries(); - - mTask.run(); - - Map<String, WrappedKeyProto.WrappedKey> rewrappedKeys = - mBackupServer.getAllTertiaryKeys(NEXT_SECONDARY_KEY_ALIAS); - SecretKey secondaryKey = (SecretKey) mRecoveryController.getKey(NEXT_SECONDARY_KEY_ALIAS); - for (String packageName : mTertiaryKeysByPackageName.keySet()) { - WrappedKeyProto.WrappedKey rewrappedKey = rewrappedKeys.get(packageName); - assertThat(KeyWrapUtils.unwrap(secondaryKey, rewrappedKey)) - .isEqualTo(mTertiaryKeysByPackageName.get(packageName)); - } - } - - @Test - public void run_persistsRewrappedKeysToDisk() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - addWrappedTertiaries(); - - mTask.run(); - - RecoverableKeyStoreSecondaryKey secondaryKey = getRecoverableKey(NEXT_SECONDARY_KEY_ALIAS); - Map<String, SecretKey> keys = - TertiaryKeyStore.newInstance(mApplication, secondaryKey).getAll(); - for (String packageName : mTertiaryKeysByPackageName.keySet()) { - SecretKey tertiaryKey = mTertiaryKeysByPackageName.get(packageName); - SecretKey newlyWrappedKey = keys.get(packageName); - assertThat(tertiaryKey.getEncoded()).isEqualTo(newlyWrappedKey.getEncoded()); - } - } - - @Test - public void run_stillSetsActiveSecondaryIfNoTertiaries() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertThat(mBackupServer.getActiveSecondaryKeyAlias().get()) - .isEqualTo(NEXT_SECONDARY_KEY_ALIAS); - } - - @Test - public void run_setsActiveSecondaryKeyAliasInSettings() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()) - .isEqualTo(NEXT_SECONDARY_KEY_ALIAS); - } - - @Test - public void run_removesNextSecondaryKeyAliasInSettings() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertFalse(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()); - } - - @Test - public void run_deletesOldKeyFromRecoverableKeyStoreLoader() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNCED); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertThat(mRecoveryController.getKey(CURRENT_SECONDARY_KEY_ALIAS)).isNull(); - } - - @Test - public void run_doesNotRotateIfNoNextAlias() throws Exception { - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - mCryptoSettings.removeNextSecondaryKeyAlias(); - - mTask.run(); - - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()) - .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS); - assertFalse(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()); - } - - @Test - public void run_doesNotRotateIfKeyIsNotSyncedYet() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()) - .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS); - } - - @Test - public void run_doesNotClearNextKeyIfSyncIsJustPending() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertThat(mCryptoSettings.getNextSecondaryKeyAlias().get()) - .isEqualTo(NEXT_SECONDARY_KEY_ALIAS); - } - - @Test - public void run_doesNotRotateIfPermanentFailure() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertThat(mCryptoSettings.getActiveSecondaryKeyAlias().get()) - .isEqualTo(CURRENT_SECONDARY_KEY_ALIAS); - } - - @Test - public void run_removesNextKeyIfPermanentFailure() throws Exception { - addNextSecondaryKeyToRecoveryController(); - setNextKeyRecoveryStatus(RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE); - addCurrentSecondaryKeyToRecoveryController(); - mCryptoSettings.setActiveSecondaryKeyAlias(CURRENT_SECONDARY_KEY_ALIAS); - mBackupServer.setActiveSecondaryKeyAlias( - CURRENT_SECONDARY_KEY_ALIAS, Collections.emptyMap()); - - mTask.run(); - - assertFalse(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()); - } - - private void setNextKeyRecoveryStatus(int status) throws Exception { - mRecoveryController.setRecoveryStatus(NEXT_SECONDARY_KEY_ALIAS, status); - } - - private void addCurrentSecondaryKeyToRecoveryController() throws Exception { - mRecoveryController.generateKey(CURRENT_SECONDARY_KEY_ALIAS); - } - - private void addNextSecondaryKeyToRecoveryController() throws Exception { - mRecoveryController.generateKey(NEXT_SECONDARY_KEY_ALIAS); - } - - private void addWrappedTertiaries() throws Exception { - TertiaryKeyStore tertiaryKeyStore = - TertiaryKeyStore.newInstance( - mApplication, getRecoverableKey(CURRENT_SECONDARY_KEY_ALIAS)); - - for (String packageName : mTertiaryKeysByPackageName.keySet()) { - tertiaryKeyStore.save(packageName, mTertiaryKeysByPackageName.get(packageName)); - } - } - - private RecoverableKeyStoreSecondaryKey getRecoverableKey(String alias) throws Exception { - return mRecoverableSecondaryKeyManager.get(alias).get(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java deleted file mode 100644 index 4ac4fa8d06c9..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTaskTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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 com.google.common.truth.Truth.assertThat; - -import android.platform.test.annotations.Presubmit; -import android.security.keystore.recovery.RecoveryController; - -import com.android.server.backup.encryption.CryptoSettings; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKey; -import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager; -import com.android.server.testing.shadows.ShadowRecoveryController; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; -import org.robolectric.annotation.Config; - -import java.security.SecureRandom; - -@RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowRecoveryController.class}) -@Presubmit -public class StartSecondaryKeyRotationTaskTest { - - private CryptoSettings mCryptoSettings; - private RecoverableKeyStoreSecondaryKeyManager mSecondaryKeyManager; - private StartSecondaryKeyRotationTask mStartSecondaryKeyRotationTask; - - @Before - public void setUp() throws Exception { - mSecondaryKeyManager = - new RecoverableKeyStoreSecondaryKeyManager( - RecoveryController.getInstance(RuntimeEnvironment.application), - new SecureRandom()); - mCryptoSettings = CryptoSettings.getInstanceForTesting(RuntimeEnvironment.application); - mStartSecondaryKeyRotationTask = - new StartSecondaryKeyRotationTask(mCryptoSettings, mSecondaryKeyManager); - - ShadowRecoveryController.reset(); - } - - @Test - public void run_doesNothingIfNoActiveSecondaryExists() { - mStartSecondaryKeyRotationTask.run(); - - assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isFalse(); - } - - @Test - public void run_doesNotRemoveExistingNextSecondaryKeyIfItIsAlreadyActive() throws Exception { - generateAnActiveKey(); - String activeAlias = mCryptoSettings.getActiveSecondaryKeyAlias().get(); - mCryptoSettings.setNextSecondaryAlias(activeAlias); - - mStartSecondaryKeyRotationTask.run(); - - assertThat(mSecondaryKeyManager.get(activeAlias).isPresent()).isTrue(); - } - - @Test - public void run_doesRemoveExistingNextSecondaryKeyIfItIsNotYetActive() throws Exception { - generateAnActiveKey(); - RecoverableKeyStoreSecondaryKey nextKey = mSecondaryKeyManager.generate(); - String nextAlias = nextKey.getAlias(); - mCryptoSettings.setNextSecondaryAlias(nextAlias); - - mStartSecondaryKeyRotationTask.run(); - - assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isFalse(); - } - - @Test - public void run_generatesANewNextSecondaryKey() throws Exception { - generateAnActiveKey(); - - mStartSecondaryKeyRotationTask.run(); - - assertThat(mCryptoSettings.getNextSecondaryKeyAlias().isPresent()).isTrue(); - } - - @Test - public void run_generatesANewKeyThatExistsInKeyStore() throws Exception { - generateAnActiveKey(); - - mStartSecondaryKeyRotationTask.run(); - - String nextAlias = mCryptoSettings.getNextSecondaryKeyAlias().get(); - assertThat(mSecondaryKeyManager.get(nextAlias).isPresent()).isTrue(); - } - - private void generateAnActiveKey() throws Exception { - RecoverableKeyStoreSecondaryKey secondaryKey = mSecondaryKeyManager.generate(); - mCryptoSettings.setActiveSecondaryKeyAlias(secondaryKey.getAlias()); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java deleted file mode 100644 index 7e9792482e0d..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * 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.testing; - -import static com.android.internal.util.Preconditions.checkArgument; - -import static java.nio.charset.StandardCharsets.UTF_8; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.RandomAccessFile; -import java.util.Locale; -import java.util.Objects; -import java.util.Optional; -import java.util.Scanner; -import java.util.regex.Pattern; - -/** - * To be used as part of a fake backup server. Processes a Scotty diff script. - * - * <p>A Scotty diff script consists of an ASCII line denoting a command, optionally followed by a - * range of bytes. Command format is either - * - * <ul> - * <li>A single 64-bit integer, followed by a new line: this denotes that the given number of - * bytes are to follow in the stream. These bytes should be written directly to the new file. - * <li>Two 64-bit integers, separated by a hyphen, followed by a new line: this says that the - * given range of bytes from the original file ought to be copied into the new file. - * </ul> - */ -public class DiffScriptProcessor { - - private static final int COPY_BUFFER_SIZE = 1024; - - private static final String READ_MODE = "r"; - private static final Pattern VALID_COMMAND_PATTERN = Pattern.compile("^\\d+(-\\d+)?$"); - - private final File mInput; - private final File mOutput; - private final long mInputLength; - - /** - * A new instance, with {@code input} as previous file, and {@code output} as new file. - * - * @param input Previous file from which ranges of bytes are to be copied. This file should be - * immutable. - * @param output Output file, to which the new data should be written. - * @throws IllegalArgumentException if input does not exist. - */ - public DiffScriptProcessor(File input, File output) { - checkArgument(input.exists(), "input file did not exist."); - mInput = input; - mInputLength = input.length(); - mOutput = Objects.requireNonNull(output); - } - - public void process(InputStream diffScript) throws IOException, MalformedDiffScriptException { - RandomAccessFile randomAccessInput = new RandomAccessFile(mInput, READ_MODE); - - try (FileOutputStream outputStream = new FileOutputStream(mOutput)) { - while (true) { - Optional<String> commandString = readCommand(diffScript); - if (!commandString.isPresent()) { - return; - } - Command command = Command.parse(commandString.get()); - - if (command.mIsRange) { - checkFileRange(command.mCount, command.mLimit); - copyRange(randomAccessInput, outputStream, command.mCount, command.mLimit); - } else { - long bytesCopied = copyBytes(diffScript, outputStream, command.mCount); - if (bytesCopied < command.mCount) { - throw new MalformedDiffScriptException( - String.format( - Locale.US, - "Command to copy %d bytes from diff script, but only %d" - + " bytes available", - command.mCount, - bytesCopied)); - } - if (diffScript.read() != '\n') { - throw new MalformedDiffScriptException("Expected new line after bytes."); - } - } - } - } - } - - private void checkFileRange(long start, long end) throws MalformedDiffScriptException { - if (end < start) { - throw new MalformedDiffScriptException( - String.format( - Locale.US, - "Command to copy %d-%d bytes from original file, but %2$d < %1$d.", - start, - end)); - } - - if (end >= mInputLength) { - throw new MalformedDiffScriptException( - String.format( - Locale.US, - "Command to copy %d-%d bytes from original file, but file is only %d" - + " bytes long.", - start, - end, - mInputLength)); - } - } - - /** - * Reads a command from the input stream. - * - * @param inputStream The input. - * @return Optional of command, or empty if EOF. - */ - private static Optional<String> readCommand(InputStream inputStream) throws IOException { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - - int b; - while (!isEndOfCommand(b = inputStream.read())) { - byteArrayOutputStream.write(b); - } - - byte[] bytes = byteArrayOutputStream.toByteArray(); - if (bytes.length == 0) { - return Optional.empty(); - } else { - return Optional.of(new String(bytes, UTF_8)); - } - } - - /** - * If the given output from {@link InputStream#read()} is the end of a command - i.e., a new - * line or the EOF. - * - * @param b The byte or -1. - * @return {@code true} if ends the command. - */ - private static boolean isEndOfCommand(int b) { - return b == -1 || b == '\n'; - } - - /** - * Copies {@code n} bytes from {@code inputStream} to {@code outputStream}. - * - * @return The number of bytes copied. - * @throws IOException if there was a problem reading or writing. - */ - private static long copyBytes(InputStream inputStream, OutputStream outputStream, long n) - throws IOException { - byte[] buffer = new byte[COPY_BUFFER_SIZE]; - long copied = 0; - while (n - copied > COPY_BUFFER_SIZE) { - long read = copyBlock(inputStream, outputStream, buffer, COPY_BUFFER_SIZE); - if (read <= 0) { - return copied; - } - } - while (n - copied > 0) { - copied += copyBlock(inputStream, outputStream, buffer, (int) (n - copied)); - } - return copied; - } - - private static long copyBlock( - InputStream inputStream, OutputStream outputStream, byte[] buffer, int size) - throws IOException { - int read = inputStream.read(buffer, 0, size); - outputStream.write(buffer, 0, read); - return read; - } - - /** - * Copies the given range of bytes from the input file to the output stream. - * - * @param input The input file. - * @param output The output stream. - * @param start Start position in the input file. - * @param end End position in the output file (inclusive). - * @throws IOException if there was a problem reading or writing. - */ - private static void copyRange(RandomAccessFile input, OutputStream output, long start, long end) - throws IOException { - input.seek(start); - - // Inefficient but obviously correct. If tests become slow, optimize. - for (; start <= end; start++) { - output.write(input.read()); - } - } - - /** Error thrown for a malformed diff script. */ - public static class MalformedDiffScriptException extends Exception { - public MalformedDiffScriptException(String message) { - super(message); - } - } - - /** - * A command telling the processor either to insert n bytes, which follow, or copy n-m bytes - * from the original file. - */ - private static class Command { - private final long mCount; - private final long mLimit; - private final boolean mIsRange; - - private Command(long count, long limit, boolean isRange) { - mCount = count; - mLimit = limit; - mIsRange = isRange; - } - - /** - * Attempts to parse the command string into a usable structure. - * - * @param command The command string, without a new line at the end. - * @throws MalformedDiffScriptException if the command is not a valid diff script command. - * @return The parsed command. - */ - private static Command parse(String command) throws MalformedDiffScriptException { - if (!VALID_COMMAND_PATTERN.matcher(command).matches()) { - throw new MalformedDiffScriptException("Bad command: " + command); - } - - Scanner commandScanner = new Scanner(command); - commandScanner.useDelimiter("-"); - long n = commandScanner.nextLong(); - if (!commandScanner.hasNextLong()) { - return new Command(n, 0L, /*isRange=*/ false); - } - long m = commandScanner.nextLong(); - return new Command(n, m, /*isRange=*/ true); - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/QueuingNonAutomaticExecutorService.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/QueuingNonAutomaticExecutorService.java deleted file mode 100644 index 9d2272e29945..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/QueuingNonAutomaticExecutorService.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.testing; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.AbstractExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * ExecutorService which needs to be stepped through the jobs in its' queue. - * - * <p>This is a deliberately simple implementation because it's only used in testing. The queued - * jobs are run on the main thread to eliminate any race condition bugs. - */ -public class QueuingNonAutomaticExecutorService extends AbstractExecutorService { - - private List<Runnable> mWaitingJobs = new ArrayList<>(); - private int mWaitingJobCount = 0; - - @Override - public void shutdown() { - mWaitingJobCount = mWaitingJobs.size(); - mWaitingJobs = null; // This will force an error if jobs are submitted after shutdown - } - - @Override - public List<Runnable> shutdownNow() { - List<Runnable> queuedJobs = mWaitingJobs; - shutdown(); - return queuedJobs; - } - - @Override - public boolean isShutdown() { - return mWaitingJobs == null; - } - - @Override - public boolean isTerminated() { - return mWaitingJobs == null && mWaitingJobCount == 0; - } - - @Override - public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { - long expiry = System.currentTimeMillis() + unit.toMillis(timeout); - for (Runnable job : mWaitingJobs) { - if (System.currentTimeMillis() > expiry) { - return false; - } - - job.run(); - } - return true; - } - - @Override - public void execute(Runnable command) { - mWaitingJobs.add(command); - } - - public void runNext() { - if (mWaitingJobs.isEmpty()) { - throw new IllegalStateException("Attempted to run jobs on an empty paused executor"); - } - - mWaitingJobs.remove(0).run(); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/testing/RandomInputStream.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/testing/RandomInputStream.java deleted file mode 100644 index 998da0bf9696..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/testing/RandomInputStream.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.testing; - -import static com.android.internal.util.Preconditions.checkArgument; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Random; - -/** {@link InputStream} that generates random bytes up to a given length. For testing purposes. */ -public class RandomInputStream extends InputStream { - private static final int BYTE_MAX_VALUE = 255; - - private final Random mRandom; - private final int mSizeBytes; - private int mBytesRead; - - /** - * A new instance, generating {@code sizeBytes} from {@code random} as a source. - * - * @param random Source of random bytes. - * @param sizeBytes The number of bytes to generate before closing the stream. - */ - public RandomInputStream(Random random, int sizeBytes) { - mRandom = random; - mSizeBytes = sizeBytes; - mBytesRead = 0; - } - - @Override - public int read() throws IOException { - if (isFinished()) { - return -1; - } - mBytesRead++; - return mRandom.nextInt(BYTE_MAX_VALUE); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - checkArgument(off + len <= b.length); - if (isFinished()) { - return -1; - } - int length = Math.min(len, mSizeBytes - mBytesRead); - int end = off + length; - - for (int i = off; i < end; ) { - for (int rnd = mRandom.nextInt(), n = Math.min(end - i, Integer.SIZE / Byte.SIZE); - n-- > 0; - rnd >>= Byte.SIZE) { - b[i++] = (byte) rnd; - } - } - - mBytesRead += length; - return length; - } - - private boolean isFinished() { - return mBytesRead >= mSizeBytes; - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java deleted file mode 100644 index b0c02ba637e0..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/CryptoTestUtils.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2018 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.testing; - -import android.util.Pair; - -import com.android.server.backup.encryption.chunk.ChunkHash; -import com.android.server.backup.encryption.protos.nano.ChunksMetadataProto; -import com.android.server.backup.encryption.protos.nano.KeyValuePairProto; - -import java.nio.charset.Charset; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; - -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; - -/** Helpers for crypto code tests. */ -public class CryptoTestUtils { - private static final String KEY_ALGORITHM = "AES"; - private static final int KEY_SIZE_BITS = 256; - - private CryptoTestUtils() {} - - public static SecretKey generateAesKey() throws NoSuchAlgorithmException { - KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM); - keyGenerator.init(KEY_SIZE_BITS); - return keyGenerator.generateKey(); - } - - /** Generates a byte array of size {@code n} containing random bytes. */ - public static byte[] generateRandomBytes(int n) { - byte[] bytes = new byte[n]; - Random random = new Random(); - random.nextBytes(bytes); - return bytes; - } - - public static ChunksMetadataProto.Chunk newChunk(ChunkHash hash, int length) { - return newChunk(hash.getHash(), length); - } - - public static ChunksMetadataProto.Chunk newChunk(byte[] hash, int length) { - ChunksMetadataProto.Chunk newChunk = new ChunksMetadataProto.Chunk(); - newChunk.hash = Arrays.copyOf(hash, hash.length); - newChunk.length = length; - return newChunk; - } - - public static ChunksMetadataProto.ChunkListing newChunkListing( - String docId, - byte[] fingerprintSalt, - int cipherType, - int orderingType, - ChunksMetadataProto.Chunk... chunks) { - ChunksMetadataProto.ChunkListing chunkListing = - newChunkListingWithoutDocId(fingerprintSalt, cipherType, orderingType, chunks); - chunkListing.documentId = docId; - return chunkListing; - } - - public static ChunksMetadataProto.ChunkListing newChunkListingWithoutDocId( - byte[] fingerprintSalt, - int cipherType, - int orderingType, - ChunksMetadataProto.Chunk... chunks) { - ChunksMetadataProto.ChunkListing chunkListing = new ChunksMetadataProto.ChunkListing(); - chunkListing.fingerprintMixerSalt = - fingerprintSalt == null - ? null - : Arrays.copyOf(fingerprintSalt, fingerprintSalt.length); - chunkListing.cipherType = cipherType; - chunkListing.chunkOrderingType = orderingType; - chunkListing.chunks = chunks; - return chunkListing; - } - - public static ChunksMetadataProto.ChunkOrdering newChunkOrdering( - int[] starts, byte[] checksum) { - ChunksMetadataProto.ChunkOrdering chunkOrdering = new ChunksMetadataProto.ChunkOrdering(); - chunkOrdering.starts = starts == null ? null : Arrays.copyOf(starts, starts.length); - chunkOrdering.checksum = - checksum == null ? checksum : Arrays.copyOf(checksum, checksum.length); - return chunkOrdering; - } - - public static ChunksMetadataProto.ChunksMetadata newChunksMetadata( - int cipherType, int checksumType, int chunkOrderingType, byte[] chunkOrdering) { - ChunksMetadataProto.ChunksMetadata metadata = new ChunksMetadataProto.ChunksMetadata(); - metadata.cipherType = cipherType; - metadata.checksumType = checksumType; - metadata.chunkOrdering = Arrays.copyOf(chunkOrdering, chunkOrdering.length); - metadata.chunkOrderingType = chunkOrderingType; - return metadata; - } - - public static KeyValuePairProto.KeyValuePair newPair(String key, String value) { - return newPair(key, value.getBytes(Charset.forName("UTF-8"))); - } - - public static KeyValuePairProto.KeyValuePair newPair(String key, byte[] value) { - KeyValuePairProto.KeyValuePair newPair = new KeyValuePairProto.KeyValuePair(); - newPair.key = key; - newPair.value = value; - return newPair; - } - - public static ChunksMetadataProto.ChunkListing clone( - ChunksMetadataProto.ChunkListing original) { - ChunksMetadataProto.Chunk[] clonedChunks; - if (original.chunks == null) { - clonedChunks = null; - } else { - clonedChunks = new ChunksMetadataProto.Chunk[original.chunks.length]; - for (int i = 0; i < original.chunks.length; i++) { - clonedChunks[i] = clone(original.chunks[i]); - } - } - - return newChunkListing( - original.documentId, - original.fingerprintMixerSalt, - original.cipherType, - original.chunkOrderingType, - clonedChunks); - } - - public static ChunksMetadataProto.Chunk clone(ChunksMetadataProto.Chunk original) { - return newChunk(original.hash, original.length); - } - - public static ChunksMetadataProto.ChunksMetadata clone( - ChunksMetadataProto.ChunksMetadata original) { - ChunksMetadataProto.ChunksMetadata cloneMetadata = new ChunksMetadataProto.ChunksMetadata(); - cloneMetadata.chunkOrderingType = original.chunkOrderingType; - cloneMetadata.chunkOrdering = - original.chunkOrdering == null - ? null - : Arrays.copyOf(original.chunkOrdering, original.chunkOrdering.length); - cloneMetadata.checksumType = original.checksumType; - cloneMetadata.cipherType = original.cipherType; - return cloneMetadata; - } - - public static ChunksMetadataProto.ChunkOrdering clone( - ChunksMetadataProto.ChunkOrdering original) { - ChunksMetadataProto.ChunkOrdering clone = new ChunksMetadataProto.ChunkOrdering(); - clone.starts = Arrays.copyOf(original.starts, original.starts.length); - clone.checksum = Arrays.copyOf(original.checksum, original.checksum.length); - return clone; - } - - public static <K, V> Map<K, V> mapOf(Pair<K, V>... pairs) { - Map<K, V> map = new HashMap<>(); - for (Pair<K, V> pair : pairs) { - map.put(pair.first, pair.second); - } - return map; - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java deleted file mode 100644 index e5d73ba41902..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/TestFileUtils.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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. - */ - -import com.google.common.io.ByteStreams; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - -/** Utility methods for use in tests */ -public class TestFileUtils { - /** Read the contents of a file into a byte array */ - public static byte[] toByteArray(File file) throws IOException { - try (FileInputStream fis = new FileInputStream(file)) { - return ByteStreams.toByteArray(fis); - } - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServer.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServer.java deleted file mode 100644 index 332906033b6d..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServer.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2018 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.testing.fakes; - -import android.annotation.Nullable; - -import com.android.internal.annotations.GuardedBy; -import com.android.server.backup.encryption.client.CryptoBackupServer; -import com.android.server.backup.encryption.client.UnexpectedActiveSecondaryOnServerException; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; - -/** Fake {@link CryptoBackupServer}, for tests. Stores tertiary keys in memory. */ -public class FakeCryptoBackupServer implements CryptoBackupServer { - @GuardedBy("this") - @Nullable - private String mActiveSecondaryKeyAlias; - - // Secondary key alias -> (package name -> tertiary key) - @GuardedBy("this") - private Map<String, Map<String, WrappedKeyProto.WrappedKey>> mWrappedKeyStore = new HashMap<>(); - - @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) { - throw new UnsupportedOperationException(); - } - - @Override - public synchronized void setActiveSecondaryKeyAlias( - String keyAlias, Map<String, WrappedKeyProto.WrappedKey> tertiaryKeys) { - mActiveSecondaryKeyAlias = keyAlias; - - mWrappedKeyStore.putIfAbsent(keyAlias, new HashMap<>()); - Map<String, WrappedKeyProto.WrappedKey> keyStore = mWrappedKeyStore.get(keyAlias); - - for (String packageName : tertiaryKeys.keySet()) { - keyStore.put(packageName, tertiaryKeys.get(packageName)); - } - } - - public synchronized Optional<String> getActiveSecondaryKeyAlias() { - return Optional.ofNullable(mActiveSecondaryKeyAlias); - } - - public synchronized Map<String, WrappedKeyProto.WrappedKey> getAllTertiaryKeys( - String secondaryKeyAlias) throws UnexpectedActiveSecondaryOnServerException { - if (!secondaryKeyAlias.equals(mActiveSecondaryKeyAlias)) { - throw new UnexpectedActiveSecondaryOnServerException( - String.format( - Locale.US, - "Requested tertiary keys wrapped with %s but %s was active secondary.", - secondaryKeyAlias, - mActiveSecondaryKeyAlias)); - } - - if (!mWrappedKeyStore.containsKey(secondaryKeyAlias)) { - return Collections.emptyMap(); - } - return new HashMap<>(mWrappedKeyStore.get(secondaryKeyAlias)); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServerTest.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServerTest.java deleted file mode 100644 index 4cd8333b1a5e..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/fakes/FakeCryptoBackupServerTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * 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.testing.fakes; - -import static com.google.common.truth.Truth.assertThat; - -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertThrows; - -import android.util.Pair; - -import com.android.server.backup.encryption.client.UnexpectedActiveSecondaryOnServerException; -import com.android.server.backup.encryption.protos.nano.WrappedKeyProto; - -import org.junit.Before; -import org.junit.Test; - -import java.nio.charset.Charset; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -public class FakeCryptoBackupServerTest { - private static final String PACKAGE_NAME_1 = "package1"; - private static final String PACKAGE_NAME_2 = "package2"; - private static final String PACKAGE_NAME_3 = "package3"; - private static final WrappedKeyProto.WrappedKey PACKAGE_KEY_1 = createWrappedKey("key1"); - private static final WrappedKeyProto.WrappedKey PACKAGE_KEY_2 = createWrappedKey("key2"); - private static final WrappedKeyProto.WrappedKey PACKAGE_KEY_3 = createWrappedKey("key3"); - - private FakeCryptoBackupServer mServer; - - @Before - public void setUp() { - mServer = new FakeCryptoBackupServer(); - } - - @Test - public void getActiveSecondaryKeyAlias_isInitiallyAbsent() throws Exception { - assertFalse(mServer.getActiveSecondaryKeyAlias().isPresent()); - } - - @Test - public void setActiveSecondaryKeyAlias_setsTheKeyAlias() throws Exception { - String keyAlias = "test"; - mServer.setActiveSecondaryKeyAlias(keyAlias, Collections.emptyMap()); - assertThat(mServer.getActiveSecondaryKeyAlias().get()).isEqualTo(keyAlias); - } - - @Test - public void getAllTertiaryKeys_returnsWrappedKeys() throws Exception { - Map<String, WrappedKeyProto.WrappedKey> entries = - createKeyMap( - new Pair<>(PACKAGE_NAME_1, PACKAGE_KEY_1), - new Pair<>(PACKAGE_NAME_2, PACKAGE_KEY_2)); - String secondaryKeyAlias = "doge"; - mServer.setActiveSecondaryKeyAlias(secondaryKeyAlias, entries); - - assertThat(mServer.getAllTertiaryKeys(secondaryKeyAlias)).containsExactlyEntriesIn(entries); - } - - @Test - public void addTertiaryKeys_updatesExistingSet() throws Exception { - String keyId = "karlin"; - WrappedKeyProto.WrappedKey replacementKey = createWrappedKey("some replacement bytes"); - - mServer.setActiveSecondaryKeyAlias( - keyId, - createKeyMap( - new Pair<>(PACKAGE_NAME_1, PACKAGE_KEY_1), - new Pair<>(PACKAGE_NAME_2, PACKAGE_KEY_2))); - - mServer.setActiveSecondaryKeyAlias( - keyId, - createKeyMap( - new Pair<>(PACKAGE_NAME_1, replacementKey), - new Pair<>(PACKAGE_NAME_3, PACKAGE_KEY_3))); - - assertThat(mServer.getAllTertiaryKeys(keyId)) - .containsExactlyEntriesIn( - createKeyMap( - new Pair<>(PACKAGE_NAME_1, replacementKey), - new Pair<>(PACKAGE_NAME_2, PACKAGE_KEY_2), - new Pair<>(PACKAGE_NAME_3, PACKAGE_KEY_3))); - } - - @Test - public void getAllTertiaryKeys_throwsForUnknownSecondaryKeyAlias() throws Exception { - assertThrows( - UnexpectedActiveSecondaryOnServerException.class, - () -> mServer.getAllTertiaryKeys("unknown")); - } - - @Test - public void uploadIncrementalBackup_throwsUnsupportedOperationException() { - assertThrows( - UnsupportedOperationException.class, - () -> - mServer.uploadIncrementalBackup( - PACKAGE_NAME_1, - "docid", - new byte[0], - new WrappedKeyProto.WrappedKey())); - } - - @Test - public void uploadNonIncrementalBackup_throwsUnsupportedOperationException() { - assertThrows( - UnsupportedOperationException.class, - () -> - mServer.uploadNonIncrementalBackup( - PACKAGE_NAME_1, new byte[0], new WrappedKeyProto.WrappedKey())); - } - - private static WrappedKeyProto.WrappedKey createWrappedKey(String data) { - WrappedKeyProto.WrappedKey wrappedKey = new WrappedKeyProto.WrappedKey(); - wrappedKey.key = data.getBytes(Charset.forName("UTF-8")); - return wrappedKey; - } - - private Map<String, WrappedKeyProto.WrappedKey> createKeyMap( - Pair<String, WrappedKeyProto.WrappedKey>... pairs) { - Map<String, WrappedKeyProto.WrappedKey> map = new HashMap<>(); - for (Pair<String, WrappedKeyProto.WrappedKey> pair : pairs) { - map.put(pair.first, pair.second); - } - return map; - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java deleted file mode 100644 index 06f4859c0881..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.testing.shadows; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Objects; - -/** - * Represents a key value pair in {@link ShadowBackupDataInput} and {@link ShadowBackupDataOutput}. - */ -public class DataEntity { - public final String mKey; - public final byte[] mValue; - public final int mSize; - - /** - * Constructs a pair with a string value. The value will be converted to a byte array in {@link - * StandardCharsets#UTF_8}. - */ - public DataEntity(String key, String value) { - this.mKey = Objects.requireNonNull(key); - this.mValue = value.getBytes(StandardCharsets.UTF_8); - mSize = this.mValue.length; - } - - /** - * Constructs a new entity with the given key but a negative size. This represents a deleted - * pair. - */ - public DataEntity(String key) { - this.mKey = Objects.requireNonNull(key); - mSize = -1; - mValue = null; - } - - /** Constructs a new entity where the size of the value is the entire array. */ - public DataEntity(String key, byte[] value) { - this(key, value, value.length); - } - - /** - * Constructs a new entity. - * - * @param key the key of the pair - * @param data the value to associate with the key - * @param size the length of the value in bytes - */ - public DataEntity(String key, byte[] data, int size) { - this.mKey = Objects.requireNonNull(key); - this.mSize = size; - mValue = new byte[size]; - for (int i = 0; i < size; i++) { - mValue[i] = data[i]; - } - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - DataEntity that = (DataEntity) o; - - if (mSize != that.mSize) { - return false; - } - if (!mKey.equals(that.mKey)) { - return false; - } - return Arrays.equals(mValue, that.mValue); - } - - @Override - public int hashCode() { - int result = mKey.hashCode(); - result = 31 * result + Arrays.hashCode(mValue); - result = 31 * result + mSize; - return result; - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowBackupDataInput.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowBackupDataInput.java deleted file mode 100644 index 7ac6ec40508d..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowBackupDataInput.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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.testing.shadows; - -import static com.google.common.base.Preconditions.checkState; - -import android.annotation.Nullable; -import android.app.backup.BackupDataInput; - -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -import java.io.ByteArrayInputStream; -import java.io.FileDescriptor; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** Shadow for BackupDataInput. */ -@Implements(BackupDataInput.class) -public class ShadowBackupDataInput { - private static final List<DataEntity> ENTITIES = new ArrayList<>(); - @Nullable private static IOException sReadNextHeaderException; - - @Nullable private ByteArrayInputStream mCurrentEntityInputStream; - private int mCurrentEntity = -1; - - /** Resets the shadow, clearing any entities or exception. */ - public static void reset() { - ENTITIES.clear(); - sReadNextHeaderException = null; - } - - /** Sets the exception which the input will throw for any call to {@link #readNextHeader}. */ - public static void setReadNextHeaderException(@Nullable IOException readNextHeaderException) { - ShadowBackupDataInput.sReadNextHeaderException = readNextHeaderException; - } - - /** Adds the given entity to the input. */ - public static void addEntity(DataEntity e) { - ENTITIES.add(e); - } - - /** Adds an entity to the input with the given key and value. */ - public static void addEntity(String key, byte[] value) { - ENTITIES.add(new DataEntity(key, value, value.length)); - } - - public void __constructor__(FileDescriptor fd) {} - - @Implementation - public boolean readNextHeader() throws IOException { - if (sReadNextHeaderException != null) { - throw sReadNextHeaderException; - } - - mCurrentEntity++; - - if (mCurrentEntity >= ENTITIES.size()) { - return false; - } - - byte[] value = ENTITIES.get(mCurrentEntity).mValue; - if (value == null) { - mCurrentEntityInputStream = new ByteArrayInputStream(new byte[0]); - } else { - mCurrentEntityInputStream = new ByteArrayInputStream(value); - } - return true; - } - - @Implementation - public String getKey() { - return ENTITIES.get(mCurrentEntity).mKey; - } - - @Implementation - public int getDataSize() { - return ENTITIES.get(mCurrentEntity).mSize; - } - - @Implementation - public void skipEntityData() { - // Do nothing. - } - - @Implementation - public int readEntityData(byte[] data, int offset, int size) { - checkState(mCurrentEntityInputStream != null, "Must call readNextHeader() first"); - return mCurrentEntityInputStream.read(data, offset, size); - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java deleted file mode 100644 index 2302e555fb44..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.testing.shadows; - -import android.app.backup.BackupDataOutput; - -import java.io.FileDescriptor; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - -import org.junit.Assert; -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -/** Shadow for BackupDataOutput. */ -@Implements(BackupDataOutput.class) -public class ShadowBackupDataOutput { - private static final List<DataEntity> ENTRIES = new ArrayList<>(); - - private String mCurrentKey; - private int mDataSize; - - public static void reset() { - ENTRIES.clear(); - } - - public static Set<DataEntity> getEntities() { - return new LinkedHashSet<>(ENTRIES); - } - - public void __constructor__(FileDescriptor fd) {} - - public void __constructor__(FileDescriptor fd, long quota) {} - - public void __constructor__(FileDescriptor fd, long quota, int transportFlags) {} - - @Implementation - public int writeEntityHeader(String key, int size) { - mCurrentKey = key; - mDataSize = size; - return 0; - } - - @Implementation - public int writeEntityData(byte[] data, int size) { - Assert.assertEquals("ShadowBackupDataOutput expects size = mDataSize", size, mDataSize); - ENTRIES.add(new DataEntity(mCurrentKey, data, mDataSize)); - return 0; - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java deleted file mode 100644 index 9c06d81ce550..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowInternalRecoveryServiceException.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2018 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.testing.shadows; - -import android.security.keystore.recovery.InternalRecoveryServiceException; - -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; - -/** Shadow {@link InternalRecoveryServiceException}. */ -@Implements(InternalRecoveryServiceException.class) -public class ShadowInternalRecoveryServiceException { - private String mMessage; - - @Implementation - public void __constructor__(String message) { - mMessage = message; - } - - @Implementation - public void __constructor__(String message, Throwable cause) { - mMessage = message; - } - - @Implementation - public String getMessage() { - return mMessage; - } -} diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowRecoveryController.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowRecoveryController.java deleted file mode 100644 index 7dad8a4e3ff3..000000000000 --- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/ShadowRecoveryController.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2018 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.testing.shadows; - -import android.content.Context; -import android.security.keystore.recovery.InternalRecoveryServiceException; -import android.security.keystore.recovery.LockScreenRequiredException; -import android.security.keystore.recovery.RecoveryController; - -import com.google.common.collect.ImmutableList; - -import org.robolectric.annotation.Implementation; -import org.robolectric.annotation.Implements; -import org.robolectric.annotation.Resetter; - -import java.lang.reflect.Constructor; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.util.HashMap; -import java.util.List; - -import javax.crypto.KeyGenerator; - -/** - * Shadow of {@link RecoveryController}. - * - * <p>Instead of generating keys via the {@link RecoveryController}, this shadow generates them in - * memory. - */ -@Implements(RecoveryController.class) -public class ShadowRecoveryController { - private static final String KEY_GENERATOR_ALGORITHM = "AES"; - private static final int KEY_SIZE_BITS = 256; - - private static boolean sIsSupported = true; - private static boolean sThrowsInternalError = false; - private static HashMap<String, Key> sKeysByAlias = new HashMap<>(); - private static HashMap<String, Integer> sKeyStatusesByAlias = new HashMap<>(); - - @Implementation - public void __constructor__() { - // do not throw - } - - @Implementation - public static RecoveryController getInstance(Context context) { - // Call non-public constructor. - try { - Constructor<RecoveryController> constructor = RecoveryController.class.getConstructor(); - return constructor.newInstance(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - @Implementation - public static boolean isRecoverableKeyStoreEnabled(Context context) { - return sIsSupported; - } - - @Implementation - public Key generateKey(String alias) - throws InternalRecoveryServiceException, LockScreenRequiredException { - maybeThrowError(); - KeyGenerator keyGenerator; - try { - keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM); - } catch (NoSuchAlgorithmException e) { - // Should never happen - throw new RuntimeException(e); - } - - keyGenerator.init(KEY_SIZE_BITS); - Key key = keyGenerator.generateKey(); - sKeysByAlias.put(alias, key); - sKeyStatusesByAlias.put(alias, RecoveryController.RECOVERY_STATUS_SYNC_IN_PROGRESS); - return key; - } - - @Implementation - public Key getKey(String alias) - throws InternalRecoveryServiceException, UnrecoverableKeyException { - return sKeysByAlias.get(alias); - } - - @Implementation - public void removeKey(String alias) throws InternalRecoveryServiceException { - sKeyStatusesByAlias.remove(alias); - sKeysByAlias.remove(alias); - } - - @Implementation - public int getRecoveryStatus(String alias) throws InternalRecoveryServiceException { - maybeThrowError(); - return sKeyStatusesByAlias.getOrDefault( - alias, RecoveryController.RECOVERY_STATUS_PERMANENT_FAILURE); - } - - @Implementation - public List<String> getAliases() throws InternalRecoveryServiceException { - return ImmutableList.copyOf(sKeyStatusesByAlias.keySet()); - } - - private static void maybeThrowError() throws InternalRecoveryServiceException { - if (sThrowsInternalError) { - throw new InternalRecoveryServiceException("test error"); - } - } - - /** Sets the recovery status of the key with {@code alias} to {@code status}. */ - public static void setRecoveryStatus(String alias, int status) { - sKeyStatusesByAlias.put(alias, status); - } - - /** Sets all existing keys to being synced. */ - public static void syncAllKeys() { - for (String alias : sKeysByAlias.keySet()) { - sKeyStatusesByAlias.put(alias, RecoveryController.RECOVERY_STATUS_SYNCED); - } - } - - public static void setThrowsInternalError(boolean throwsInternalError) { - ShadowRecoveryController.sThrowsInternalError = throwsInternalError; - } - - public static void setIsSupported(boolean isSupported) { - ShadowRecoveryController.sIsSupported = isSupported; - } - - @Resetter - public static void reset() { - sIsSupported = true; - sThrowsInternalError = false; - sKeysByAlias.clear(); - sKeyStatusesByAlias.clear(); - } -} diff --git a/packages/BackupEncryption/test/unittest/Android.bp b/packages/BackupEncryption/test/unittest/Android.bp deleted file mode 100644 index f005170884c8..000000000000 --- a/packages/BackupEncryption/test/unittest/Android.bp +++ /dev/null @@ -1,31 +0,0 @@ -package { - // See: http://go/android-license-faq - // A large-scale-change added 'default_applicable_licenses' to import - // all of the 'license_kinds' from "frameworks_base_license" - // to get the below license kinds: - // SPDX-license-identifier-Apache-2.0 - default_applicable_licenses: ["frameworks_base_license"], -} - -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", -} diff --git a/packages/BackupEncryption/test/unittest/AndroidManifest.xml b/packages/BackupEncryption/test/unittest/AndroidManifest.xml deleted file mode 100644 index 39ac8aa32ebc..000000000000 --- a/packages/BackupEncryption/test/unittest/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?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 deleted file mode 100644 index c9c812a78194..000000000000 --- a/packages/BackupEncryption/test/unittest/AndroidTest.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?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/services/Android.bp b/services/Android.bp index 637c4eea7bb3..76a148419506 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -88,7 +88,6 @@ filegroup { ":services.appwidget-sources", ":services.autofill-sources", ":services.backup-sources", - ":backuplib-sources", ":services.companion-sources", ":services.contentcapture-sources", ":services.contentsuggestions-sources", |