diff options
Diffstat (limited to 'packages/BackupEncryption/src')
3 files changed, 193 insertions, 3 deletions
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 new file mode 100644 index 000000000000..56e1c053d8e3 --- /dev/null +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/DecryptedChunkKvOutput.java @@ -0,0 +1,111 @@ +/* + * 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 new file mode 100644 index 000000000000..b3518e144ce3 --- /dev/null +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java @@ -0,0 +1,77 @@ +/* + * 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 static com.android.internal.util.Preconditions.checkNotNull; + +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; + +/** + * 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"); + checkNotNull(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/tasks/DecryptedChunkOutput.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java index e3df3c1eb96f..f67f1007f632 100644 --- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java +++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/DecryptedChunkOutput.java @@ -19,6 +19,7 @@ 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 @@ -30,7 +31,7 @@ public interface DecryptedChunkOutput extends Closeable { * * @return {@code this}, to allow use with try-with-resources */ - DecryptedChunkOutput open() throws IOException; + DecryptedChunkOutput open() throws IOException, NoSuchAlgorithmException; /** * Writes the plaintext bytes of chunk to whatever output the implementation chooses. Also @@ -43,12 +44,13 @@ public interface DecryptedChunkOutput extends Closeable { * 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; + 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(); + byte[] getDigest() throws NoSuchAlgorithmException; } |