/* * 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; } }