1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.backup.encryption.kv; 18 19 import static com.android.internal.util.Preconditions.checkState; 20 21 import com.android.internal.annotations.VisibleForTesting; 22 import com.android.server.backup.encryption.chunk.ChunkHash; 23 import com.android.server.backup.encryption.chunking.ChunkHasher; 24 import com.android.server.backup.encryption.protos.nano.KeyValuePairProto; 25 import com.android.server.backup.encryption.tasks.DecryptedChunkOutput; 26 27 import java.io.IOException; 28 import java.security.InvalidKeyException; 29 import java.security.MessageDigest; 30 import java.security.NoSuchAlgorithmException; 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.Collections; 34 import java.util.Comparator; 35 import java.util.List; 36 37 /** 38 * Builds a key value backup set from plaintext chunks. Computes a digest over the sorted SHA-256 39 * hashes of the chunks. 40 */ 41 public class DecryptedChunkKvOutput implements DecryptedChunkOutput { 42 @VisibleForTesting static final String DIGEST_ALGORITHM = "SHA-256"; 43 44 private final ChunkHasher mChunkHasher; 45 private final List<KeyValuePairProto.KeyValuePair> mUnsortedPairs = new ArrayList<>(); 46 private final List<ChunkHash> mUnsortedHashes = new ArrayList<>(); 47 private boolean mClosed; 48 49 /** Constructs a new instance which computers the digest using the given hasher. */ DecryptedChunkKvOutput(ChunkHasher chunkHasher)50 public DecryptedChunkKvOutput(ChunkHasher chunkHasher) { 51 mChunkHasher = chunkHasher; 52 } 53 54 @Override open()55 public DecryptedChunkOutput open() { 56 // As we don't have any resources there is nothing to open. 57 return this; 58 } 59 60 @Override processChunk(byte[] plaintextBuffer, int length)61 public void processChunk(byte[] plaintextBuffer, int length) 62 throws IOException, InvalidKeyException { 63 checkState(!mClosed, "Cannot process chunk after close()"); 64 KeyValuePairProto.KeyValuePair kvPair = new KeyValuePairProto.KeyValuePair(); 65 KeyValuePairProto.KeyValuePair.mergeFrom(kvPair, plaintextBuffer, 0, length); 66 mUnsortedPairs.add(kvPair); 67 // TODO(b/71492289): Update ChunkHasher to accept offset and length so we don't have to copy 68 // the buffer into a smaller array. 69 mUnsortedHashes.add(mChunkHasher.computeHash(Arrays.copyOf(plaintextBuffer, length))); 70 } 71 72 @Override close()73 public void close() { 74 // As we don't have any resources there is nothing to close. 75 mClosed = true; 76 } 77 78 @Override getDigest()79 public byte[] getDigest() throws NoSuchAlgorithmException { 80 checkState(mClosed, "Must close() before getDigest()"); 81 MessageDigest digest = getMessageDigest(); 82 Collections.sort(mUnsortedHashes); 83 for (ChunkHash hash : mUnsortedHashes) { 84 digest.update(hash.getHash()); 85 } 86 return digest.digest(); 87 } 88 getMessageDigest()89 private static MessageDigest getMessageDigest() throws NoSuchAlgorithmException { 90 return MessageDigest.getInstance(DIGEST_ALGORITHM); 91 } 92 93 /** 94 * Returns the key value pairs from the backup, sorted lexicographically by key. 95 * 96 * <p>You must call {@link #close} first. 97 */ getPairs()98 public List<KeyValuePairProto.KeyValuePair> getPairs() { 99 checkState(mClosed, "Must close() before getPairs()"); 100 Collections.sort( 101 mUnsortedPairs, 102 new Comparator<KeyValuePairProto.KeyValuePair>() { 103 @Override 104 public int compare( 105 KeyValuePairProto.KeyValuePair o1, KeyValuePairProto.KeyValuePair o2) { 106 return o1.key.compareTo(o2.key); 107 } 108 }); 109 return mUnsortedPairs; 110 } 111 } 112