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

Commit cf2b7c3b authored by Bram Bonné's avatar Bram Bonné
Browse files

Moves first parts of backup crypto code (ChunkHash class) to the framework.

Some changes were needed to the original code:
- Guava's EqualsTester tests are replaced by regular equals tests.
- Guava's primitives.UnsignedBytes.LexicographicalComparator needed to
be copied over, as no corresponding comparator exists in the framework.

Bug: 111386661
Test: atest RunFrameworksServicesRoboTests
Change-Id: I24fef4b47f7777b9be0c2e51f0be48e45b323987
parent dab9d744
Loading
Loading
Loading
Loading
+89 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.chunk;

import com.android.internal.util.Preconditions;
import java.util.Arrays;
import java.util.Base64;

/**
 * Represents the SHA-256 hash of the plaintext of a chunk, which is frequently used as a key.
 *
 * <p>This class is {@link Comparable} and implements {@link #equals(Object)} and {@link
 * #hashCode()}.
 */
public class ChunkHash implements Comparable<ChunkHash> {
    /** The length of the hash in bytes. The hash is a SHA-256, so this is 256 bits. */
    public static final int HASH_LENGTH_BYTES = 256 / 8;

    private static final int UNSIGNED_MASK = 0xFF;

    private final byte[] mHash;

    /** Constructs a new instance which wraps the given SHA-256 hash bytes. */
    public ChunkHash(byte[] hash) {
        Preconditions.checkArgument(hash.length == HASH_LENGTH_BYTES, "Hash must have 256 bits");
        mHash = hash;
    }

    public byte[] getHash() {
        return mHash;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof ChunkHash)) {
            return false;
        }

        ChunkHash chunkHash = (ChunkHash) o;
        return Arrays.equals(mHash, chunkHash.mHash);
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(mHash);
    }

    @Override
    public int compareTo(ChunkHash other) {
        return lexicographicalCompareUnsignedBytes(getHash(), other.getHash());
    }

    @Override
    public String toString() {
        return Base64.getEncoder().encodeToString(mHash);
    }

    private static int lexicographicalCompareUnsignedBytes(byte[] left, byte[] right) {
        int minLength = Math.min(left.length, right.length);
        for (int i = 0; i < minLength; i++) {
            int result = toInt(left[i]) - toInt(right[i]);
            if (result != 0) {
                return result;
            }
        }
        return left.length - right.length;
    }

    private static int toInt(byte value) {
        return value & UNSIGNED_MASK;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ LOCAL_AIDL_INCLUDES := \
LOCAL_STATIC_JAVA_LIBRARIES := \
    platform-robolectric-android-all-stubs \
    android-support-test \
    guava \
    mockito-robolectric-prebuilt \
    platform-test-annotations \
    truth-prebuilt \
+101 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.chunk;

import static com.google.common.truth.Truth.assertThat;

import android.platform.test.annotations.Presubmit;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderPackages;
import com.google.common.primitives.Bytes;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;

@RunWith(FrameworkRobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 26)
@SystemLoaderPackages({"com.android.server.backup"})
@Presubmit
public class ChunkHashTest {
    private static final int HASH_LENGTH_BYTES = 256 / 8;
    private static final byte[] TEST_HASH_1 = Arrays.copyOf(new byte[] {1}, HASH_LENGTH_BYTES);
    private static final byte[] TEST_HASH_2 = Arrays.copyOf(new byte[] {2}, HASH_LENGTH_BYTES);

    @Test
    public void testGetHash_returnsHash() {
        ChunkHash chunkHash = new ChunkHash(TEST_HASH_1);

        byte[] hash = chunkHash.getHash();

        assertThat(hash).asList().containsExactlyElementsIn(Bytes.asList(TEST_HASH_1)).inOrder();
    }

    @Test
    public void testEquals() {
        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
        ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1);
        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);

        assertThat(chunkHash1).isEqualTo(equalChunkHash1);
        assertThat(chunkHash1).isNotEqualTo(chunkHash2);
    }

    @Test
    public void testHashCode() {
        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
        ChunkHash equalChunkHash1 = new ChunkHash(TEST_HASH_1);
        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);

        int hash1 = chunkHash1.hashCode();
        int equalHash1 = equalChunkHash1.hashCode();
        int hash2 = chunkHash2.hashCode();

        assertThat(hash1).isEqualTo(equalHash1);
        assertThat(hash1).isNotEqualTo(hash2);
    }

    @Test
    public void testCompareTo_whenEqual_returnsZero() {
        ChunkHash chunkHash = new ChunkHash(TEST_HASH_1);
        ChunkHash equalChunkHash = new ChunkHash(TEST_HASH_1);

        int result = chunkHash.compareTo(equalChunkHash);

        assertThat(result).isEqualTo(0);
    }

    @Test
    public void testCompareTo_whenArgumentGreater_returnsNegative() {
        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);

        int result = chunkHash1.compareTo(chunkHash2);

        assertThat(result).isLessThan(0);
    }

    @Test
    public void testCompareTo_whenArgumentSmaller_returnsPositive() {
        ChunkHash chunkHash1 = new ChunkHash(TEST_HASH_1);
        ChunkHash chunkHash2 = new ChunkHash(TEST_HASH_2);

        int result = chunkHash2.compareTo(chunkHash1);

        assertThat(result).isGreaterThan(0);
    }
}