blob: 8db3b979cbbce076051187dccc9c832d524ca84d [file] [log] [blame]
/*
* Copyright (C) 2021 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.nearby.common.bluetooth.fastpair;
import static com.android.server.nearby.common.bluetooth.fastpair.AesCtrMultipleBlockEncryption.KEY_LENGTH;
import static com.android.server.nearby.common.bluetooth.fastpair.HmacSha256.HMAC_SHA256_BLOCK_SIZE;
import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.primitives.Bytes.concat;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.platform.test.annotations.Presubmit;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import androidx.test.filters.SmallTest;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hashing;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Random;
/**
* Unit tests for {@link HmacSha256}.
*/
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
public class HmacSha256Test {
private static final int EXTRACT_HMAC_SIZE = 8;
private static final byte OUTER_PADDING_BYTE = 0x5c;
private static final byte INNER_PADDING_BYTE = 0x36;
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void compareResultWithOurImplementation_mustBeIdentical()
throws GeneralSecurityException {
Random random = new Random(0xFE2C);
for (int i = 0; i < 1000; i++) {
byte[] secret = AesCtrMultipleBlockEncryption.generateKey();
// Avoid too small data size that may cause false alarm.
int dataLength = random.nextInt(64);
byte[] data = new byte[dataLength];
random.nextBytes(data);
assertThat(HmacSha256.build(secret, data)).isEqualTo(doHmacSha256(secret, data));
}
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputIncorrectKeySizeToDecrypt_mustThrowException() {
byte[] secret = new byte[KEY_LENGTH - 1];
byte[] data = base16().decode("1234567890ABCDEF1234567890ABCDEF1234567890ABCD");
GeneralSecurityException exception =
assertThrows(GeneralSecurityException.class, () -> HmacSha256.build(secret, data));
assertThat(exception)
.hasMessageThat()
.contains("Incorrect key length, should be the AES-128 key.");
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputTwoIdenticalArrays_compareTwoHmacMustReturnTrue() {
Random random = new Random(0x1237);
byte[] array1 = new byte[EXTRACT_HMAC_SIZE];
random.nextBytes(array1);
byte[] array2 = Arrays.copyOf(array1, array1.length);
assertThat(HmacSha256.compareTwoHMACs(array1, array2)).isTrue();
}
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputTwoRandomArrays_compareTwoHmacMustReturnFalse() {
Random random = new Random(0xff);
byte[] array1 = new byte[EXTRACT_HMAC_SIZE];
random.nextBytes(array1);
byte[] array2 = new byte[EXTRACT_HMAC_SIZE];
random.nextBytes(array2);
assertThat(HmacSha256.compareTwoHMACs(array1, array2)).isFalse();
}
// HMAC-SHA256 may not be previously defined on Bluetooth platforms, so we explicitly create
// the code on test case. This will allow us to easily detect where partner implementation might
// have gone wrong or where our spec isn't clear enough.
static byte[] doHmacSha256(byte[] key, byte[] data) {
Preconditions.checkArgument(
key != null && key.length == KEY_LENGTH && data != null,
"Parameters can't be null.");
// Performs SHA256(concat((key ^ opad),SHA256(concat((key ^ ipad), data)))), where
// key is the given secret extended to 64 bytes by concat(secret, ZEROS).
// opad is 64 bytes outer padding, consisting of repeated bytes valued 0x5c.
// ipad is 64 bytes inner padding, consisting of repeated bytes valued 0x36.
byte[] keyIpad = new byte[HMAC_SHA256_BLOCK_SIZE];
byte[] keyOpad = new byte[HMAC_SHA256_BLOCK_SIZE];
for (int i = 0; i < KEY_LENGTH; i++) {
keyIpad[i] = (byte) (key[i] ^ INNER_PADDING_BYTE);
keyOpad[i] = (byte) (key[i] ^ OUTER_PADDING_BYTE);
}
Arrays.fill(keyIpad, KEY_LENGTH, HMAC_SHA256_BLOCK_SIZE, INNER_PADDING_BYTE);
Arrays.fill(keyOpad, KEY_LENGTH, HMAC_SHA256_BLOCK_SIZE, OUTER_PADDING_BYTE);
byte[] innerSha256Result = Hashing.sha256().hashBytes(concat(keyIpad, data)).asBytes();
return Hashing.sha256().hashBytes(concat(keyOpad, innerSha256Result)).asBytes();
}
// Adds this test example on spec. Also we can easily change the parameters(e.g. secret, data)
// to clarify test results with partners.
@Test
@SdkSuppress(minSdkVersion = 32, codeName = "T")
public void inputTestExampleToHmacSha256_getCorrectResult() {
byte[] secret = base16().decode("0123456789ABCDEF0123456789ABCDEF");
byte[] data =
base16().decode(
"0001020304050607EE4A2483738052E44E9B2A145E5DDFAA44B9E5536AF438E1E5C6");
byte[] hmacResult = doHmacSha256(secret, data);
assertThat(hmacResult)
.isEqualTo(base16().decode(
"55EC5E6055AF6E92618B7D8710D4413709AB5DA27CA26A66F52E5AD4E8209052"));
}
}