Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 004e85f7 authored by Ruslan Tkhakokhov's avatar Ruslan Tkhakokhov Committed by Al Sutton
Browse files

Route EncryptedLocalTransport KV backup/restore through encryption code

Bug: 142227548
Test: Verify the device boots successfully
      Verify EncryptedLocalTransport APK is present
      Verify manual backup/restore using bmgr for LocalTransport and EncryptedLocalTransport
      For LocalTransport (unencrypted) and EncryptedLocalTransport:
        atest CtsBackupTestCases
        atest CtsBackupHostTestCases
        atest GtsBackupTestCases
        atest GtsBackupHostTestCases


Change-Id: Iac3a8a50d7f761442a4b784cfba3a980e900dd7f
parent d0844929
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -29,6 +29,8 @@
         'service' attribute here is a flattened ComponentName string. -->
         'service' attribute here is a flattened ComponentName string. -->
    <backup-transport-whitelisted-service
    <backup-transport-whitelisted-service
        service="com.android.localtransport/.LocalTransportService" />
        service="com.android.localtransport/.LocalTransportService" />
    <backup-transport-whitelisted-service
        service="com.android.encryptedlocaltransport/.EncryptedLocalTransportService" />


    <!-- Whitelist Shell to use the bugreport API -->
    <!-- Whitelist Shell to use the bugreport API -->
    <bugreport-whitelisted package="com.android.shell" />
    <bugreport-whitelisted package="com.android.shell" />
+1 −2
Original line number Original line Diff line number Diff line
@@ -17,8 +17,7 @@
android_app {
android_app {
    name: "BackupEncryption",
    name: "BackupEncryption",
    srcs: ["src/**/*.java"],
    srcs: ["src/**/*.java"],
    libs: ["backup-encryption-protos"],
    static_libs: ["backup-encryption-protos", "backuplib"],
    static_libs: ["backuplib"],
    optimize: { enabled: false },
    optimize: { enabled: false },
    platform_apis: true,
    platform_apis: true,
    certificate: "platform",
    certificate: "platform",
+81 −0
Original line number Original line Diff line number Diff line
/*
 * 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();
    }
}
+143 −0
Original line number Original line Diff line number Diff line
/*
 * 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.server.backup.encryption.BackupEncryptionService.TAG;

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 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");
            }
        }
    }
}
+144 −1
Original line number Original line Diff line number Diff line
@@ -18,27 +18,58 @@ package com.android.server.backup.encryption.transport;


import static com.android.server.backup.encryption.BackupEncryptionService.TAG;
import static com.android.server.backup.encryption.BackupEncryptionService.TAG;


import android.app.backup.BackupTransport;
import android.app.backup.RestoreDescription;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.RemoteException;
import android.util.Log;
import android.util.Log;


import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.encryption.KeyValueEncrypter;
import com.android.server.backup.transport.DelegatingTransport;
import com.android.server.backup.transport.DelegatingTransport;
import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.transport.TransportClient;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicReference;

/**
/**
 * This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
 * This is an implementation of {@link IBackupTransport} that encrypts (or decrypts) the data when
 * sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
 * sending it (or receiving it) from the {@link IBackupTransport} returned by {@link
 * TransportClient.connect(String)}.
 * TransportClient.connect(String)}.
 */
 */
public class IntermediateEncryptingTransport extends DelegatingTransport {
public class IntermediateEncryptingTransport extends DelegatingTransport {
    private static final String BACKUP_TEMP_DIR = "backup";
    private static final String RESTORE_TEMP_DIR = "restore";

    private final TransportClient mTransportClient;
    private final TransportClient mTransportClient;
    private final Object mConnectLock = new Object();
    private final Object mConnectLock = new Object();
    private final Context mContext;
    private volatile IBackupTransport mRealTransport;
    private volatile IBackupTransport mRealTransport;
    private AtomicReference<String> mNextRestorePackage = new AtomicReference<>();
    private final KeyValueEncrypter mKeyValueEncrypter;
    private final boolean mShouldEncrypt;

    IntermediateEncryptingTransport(
            TransportClient transportClient, Context context, boolean shouldEncrypt) {
        this(transportClient, context, new KeyValueEncrypter(context), shouldEncrypt);
    }


    @VisibleForTesting
    @VisibleForTesting
    IntermediateEncryptingTransport(TransportClient transportClient) {
    IntermediateEncryptingTransport(
            TransportClient transportClient, Context context, KeyValueEncrypter keyValueEncrypter,
            boolean shouldEncrypt) {
        mTransportClient = transportClient;
        mTransportClient = transportClient;
        mContext = context;
        mKeyValueEncrypter = keyValueEncrypter;
        mShouldEncrypt = shouldEncrypt;
    }
    }


    @Override
    @Override
@@ -46,9 +77,116 @@ public class IntermediateEncryptingTransport extends DelegatingTransport {
        if (mRealTransport == null) {
        if (mRealTransport == null) {
            connect();
            connect();
        }
        }
        Log.d(TAG, "real transport = " + mRealTransport.name());
        return mRealTransport;
        return mRealTransport;
    }
    }


    @Override
    public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
            throws RemoteException {
        if (!mShouldEncrypt) {
            return super.performBackup(packageInfo, inFd, flags);
        }

        File encryptedStorageFile = getBackupTempStorage(packageInfo.packageName);
        if (encryptedStorageFile == null) {
            return BackupTransport.TRANSPORT_ERROR;
        }

        // Encrypt the backup data and write it into a temp file.
        try (OutputStream encryptedOutput = new FileOutputStream(encryptedStorageFile)) {
            mKeyValueEncrypter.encryptKeyValueData(packageInfo.packageName, inFd,
                    encryptedOutput);
        } catch (Throwable e) {
            Log.e(TAG, "Failed to encrypt backup data: ", e);
            return BackupTransport.TRANSPORT_ERROR;
        }

        // Pass the temp file to the real transport for backup.
        try (FileInputStream encryptedInput = new FileInputStream(encryptedStorageFile)) {
            return super.performBackup(
                    packageInfo, ParcelFileDescriptor.dup(encryptedInput.getFD()), flags);
        } catch (IOException e) {
            Log.e(TAG, "Failed to read encrypted data from temp storage: ", e);
            return BackupTransport.TRANSPORT_ERROR;
        }
    }

    @Override
    public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
        if (!mShouldEncrypt) {
            return super.getRestoreData(outFd);
        }

        String nextRestorePackage = mNextRestorePackage.get();
        if (nextRestorePackage == null) {
            Log.e(TAG, "No next restore package set");
            return BackupTransport.TRANSPORT_ERROR;
        }

        File encryptedStorageFile = getRestoreTempStorage(nextRestorePackage);
        if (encryptedStorageFile == null) {
            return BackupTransport.TRANSPORT_ERROR;
        }

        // Get encrypted restore data from the real transport and write it into a temp file.
        try (FileOutputStream outputStream = new FileOutputStream(encryptedStorageFile)) {
            int status = super.getRestoreData(ParcelFileDescriptor.dup(outputStream.getFD()));
            if (status != BackupTransport.TRANSPORT_OK) {
                Log.e(TAG, "Failed to read restore data from transport, status = " + status);
                return status;
            }
        } catch (IOException e) {
            Log.e(TAG, "Failed to write encrypted data to temp storage: ", e);
            return BackupTransport.TRANSPORT_ERROR;
        }

        // Decrypt the data and write it into the fd given by the real transport.
        try (InputStream inputStream = new FileInputStream(encryptedStorageFile)) {
            mKeyValueEncrypter.decryptKeyValueData(nextRestorePackage, inputStream, outFd);
            encryptedStorageFile.delete();
        } catch (Exception e) {
            Log.e(TAG, "Failed to decrypt restored data: ", e);
            return BackupTransport.TRANSPORT_ERROR;
        }

        return BackupTransport.TRANSPORT_OK;
    }

    @Override
    public RestoreDescription nextRestorePackage() throws RemoteException {
        if (!mShouldEncrypt) {
            return super.nextRestorePackage();
        }

        RestoreDescription restoreDescription = super.nextRestorePackage();
        mNextRestorePackage.set(restoreDescription.getPackageName());

        return restoreDescription;
    }

    @VisibleForTesting
    protected File getBackupTempStorage(String packageName) {
        return getTempStorage(packageName, BACKUP_TEMP_DIR);
    }

    @VisibleForTesting
    protected File getRestoreTempStorage(String packageName) {
        return getTempStorage(packageName, RESTORE_TEMP_DIR);
    }

    private File getTempStorage(String packageName, String operationType) {
        File encryptedDir = new File(mContext.getFilesDir(), operationType);
        encryptedDir.mkdir();
        File encryptedFile = new File(encryptedDir, packageName);
        try {
            encryptedFile.createNewFile();
        } catch (IOException e) {
            Log.e(TAG, "Failed to create temp file for encrypted data: ", e);
        }
        return encryptedFile;
    }

    private void connect() throws RemoteException {
    private void connect() throws RemoteException {
        Log.i(TAG, "connecting " + mTransportClient);
        Log.i(TAG, "connecting " + mTransportClient);
        synchronized (mConnectLock) {
        synchronized (mConnectLock) {
@@ -65,4 +203,9 @@ public class IntermediateEncryptingTransport extends DelegatingTransport {
    TransportClient getClient() {
    TransportClient getClient() {
        return mTransportClient;
        return mTransportClient;
    }
    }

    @VisibleForTesting
    void setNextRestorePackage(String nextRestorePackage) {
        mNextRestorePackage.set(nextRestorePackage);
    }
}
}
Loading