blob: 04381af561b2d4a90ffd7505f98c905f1582e9e0 [file] [log] [blame]
/*
* 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();
}
}