Merge tag 'LA.QSSI.14.0.r1-16000-qssi.0' of https://git.codelinaro.org/clo/la/platform/packages/modules/Connectivity into uvite
LA.QSSI.14.0.r1-16000-qssi.0
This commit is contained in:
commit
f0455632dc
|
@ -39,10 +39,15 @@ public final class DataElement implements Parcelable {
|
|||
private final int mKey;
|
||||
private final byte[] mValue;
|
||||
|
||||
/** @hide */
|
||||
/**
|
||||
* Note this interface is used for internal implementation only.
|
||||
* We only keep those data element types used for encoding and decoding from the specs.
|
||||
* Please read the nearby specs for learning more about each data type and use it as the only
|
||||
* source.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@IntDef({
|
||||
DataType.BLE_SERVICE_DATA,
|
||||
DataType.BLE_ADDRESS,
|
||||
DataType.SALT,
|
||||
DataType.PRIVATE_IDENTITY,
|
||||
DataType.TRUSTED_IDENTITY,
|
||||
|
@ -50,20 +55,17 @@ public final class DataElement implements Parcelable {
|
|||
DataType.PROVISIONED_IDENTITY,
|
||||
DataType.TX_POWER,
|
||||
DataType.ACTION,
|
||||
DataType.MODEL_ID,
|
||||
DataType.EDDYSTONE_EPHEMERAL_IDENTIFIER,
|
||||
DataType.ACCOUNT_KEY_DATA,
|
||||
DataType.CONNECTION_STATUS,
|
||||
DataType.BATTERY,
|
||||
DataType.ENCRYPTION_INFO,
|
||||
DataType.BLE_SERVICE_DATA,
|
||||
DataType.BLE_ADDRESS,
|
||||
DataType.SCAN_MODE,
|
||||
DataType.TEST_DE_BEGIN,
|
||||
DataType.TEST_DE_END
|
||||
})
|
||||
public @interface DataType {
|
||||
int BLE_SERVICE_DATA = 100;
|
||||
int BLE_ADDRESS = 101;
|
||||
// This is to indicate if the scan is offload only
|
||||
int SCAN_MODE = 102;
|
||||
int SALT = 0;
|
||||
int PRIVATE_IDENTITY = 1;
|
||||
int TRUSTED_IDENTITY = 2;
|
||||
|
@ -71,11 +73,19 @@ public final class DataElement implements Parcelable {
|
|||
int PROVISIONED_IDENTITY = 4;
|
||||
int TX_POWER = 5;
|
||||
int ACTION = 6;
|
||||
int MODEL_ID = 7;
|
||||
int EDDYSTONE_EPHEMERAL_IDENTIFIER = 8;
|
||||
int ACCOUNT_KEY_DATA = 9;
|
||||
int CONNECTION_STATUS = 10;
|
||||
int BATTERY = 11;
|
||||
|
||||
int ENCRYPTION_INFO = 16;
|
||||
|
||||
// Not defined in the spec. Reserved for internal use only.
|
||||
int BLE_SERVICE_DATA = 100;
|
||||
int BLE_ADDRESS = 101;
|
||||
// This is to indicate if the scan is offload only
|
||||
int SCAN_MODE = 102;
|
||||
|
||||
int DEVICE_TYPE = 22;
|
||||
// Reserves test DE ranges from {@link DataElement.DataType#TEST_DE_BEGIN}
|
||||
// to {@link DataElement.DataType#TEST_DE_END}, inclusive.
|
||||
// Reserves 128 Test DEs.
|
||||
|
@ -83,27 +93,6 @@ public final class DataElement implements Parcelable {
|
|||
int TEST_DE_END = Integer.MAX_VALUE; // 2147483647
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public static boolean isValidType(int type) {
|
||||
return type == DataType.BLE_SERVICE_DATA
|
||||
|| type == DataType.ACCOUNT_KEY_DATA
|
||||
|| type == DataType.BLE_ADDRESS
|
||||
|| type == DataType.SCAN_MODE
|
||||
|| type == DataType.SALT
|
||||
|| type == DataType.PRIVATE_IDENTITY
|
||||
|| type == DataType.TRUSTED_IDENTITY
|
||||
|| type == DataType.PUBLIC_IDENTITY
|
||||
|| type == DataType.PROVISIONED_IDENTITY
|
||||
|| type == DataType.TX_POWER
|
||||
|| type == DataType.ACTION
|
||||
|| type == DataType.MODEL_ID
|
||||
|| type == DataType.EDDYSTONE_EPHEMERAL_IDENTIFIER
|
||||
|| type == DataType.CONNECTION_STATUS
|
||||
|| type == DataType.BATTERY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if this is identity type.
|
||||
* @hide
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (C) 2023 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.presence;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.Nullable;
|
||||
import android.nearby.DataElement;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.server.nearby.util.ArrayUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Data Element that indicates the presence of extended 16 bytes salt instead of 2 byte salt and to
|
||||
* indicate which encoding scheme (MIC tag or Signature) is used for a section. If present, it must
|
||||
* be the first DE in a section.
|
||||
*/
|
||||
public class EncryptionInfo {
|
||||
|
||||
@IntDef({EncodingScheme.MIC, EncodingScheme.SIGNATURE})
|
||||
public @interface EncodingScheme {
|
||||
int MIC = 0;
|
||||
int SIGNATURE = 1;
|
||||
}
|
||||
|
||||
// 1st byte : encryption scheme
|
||||
// 2nd to 17th bytes: salt
|
||||
public static final int ENCRYPTION_INFO_LENGTH = 17;
|
||||
private static final int ENCODING_SCHEME_MASK = 0b01111000;
|
||||
private static final int ENCODING_SCHEME_OFFSET = 3;
|
||||
@EncodingScheme
|
||||
private final int mEncodingScheme;
|
||||
private final byte[] mSalt;
|
||||
|
||||
/**
|
||||
* Constructs a {@link DataElement}.
|
||||
*/
|
||||
public EncryptionInfo(@NonNull byte[] value) {
|
||||
Preconditions.checkArgument(value.length == ENCRYPTION_INFO_LENGTH);
|
||||
mEncodingScheme = (value[0] & ENCODING_SCHEME_MASK) >> ENCODING_SCHEME_OFFSET;
|
||||
Preconditions.checkArgument(isValidEncodingScheme(mEncodingScheme));
|
||||
mSalt = Arrays.copyOfRange(value, 1, ENCRYPTION_INFO_LENGTH);
|
||||
}
|
||||
|
||||
private boolean isValidEncodingScheme(int scheme) {
|
||||
return scheme == EncodingScheme.MIC || scheme == EncodingScheme.SIGNATURE;
|
||||
}
|
||||
|
||||
@EncodingScheme
|
||||
public int getEncodingScheme() {
|
||||
return mEncodingScheme;
|
||||
}
|
||||
|
||||
public byte[] getSalt() {
|
||||
return mSalt;
|
||||
}
|
||||
|
||||
/** Combines the encoding scheme and salt to a byte array
|
||||
* that represents an {@link EncryptionInfo}.
|
||||
*/
|
||||
@Nullable
|
||||
public static byte[] toByte(@EncodingScheme int encodingScheme, byte[] salt) {
|
||||
if (ArrayUtils.isEmpty(salt)) {
|
||||
return null;
|
||||
}
|
||||
if (salt.length != ENCRYPTION_INFO_LENGTH - 1) {
|
||||
return null;
|
||||
}
|
||||
byte schemeByte =
|
||||
(byte) ((encodingScheme << ENCODING_SCHEME_OFFSET) & ENCODING_SCHEME_MASK);
|
||||
return ArrayUtils.append(schemeByte, salt);
|
||||
}
|
||||
}
|
|
@ -16,22 +16,27 @@
|
|||
|
||||
package com.android.server.nearby.presence;
|
||||
|
||||
import static android.nearby.BroadcastRequest.PRESENCE_VERSION_V1;
|
||||
|
||||
import static com.android.server.nearby.NearbyService.TAG;
|
||||
import static com.android.server.nearby.presence.EncryptionInfo.ENCRYPTION_INFO_LENGTH;
|
||||
import static com.android.server.nearby.presence.PresenceConstants.PRESENCE_UUID_BYTES;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.nearby.BroadcastRequest;
|
||||
import android.nearby.BroadcastRequest.BroadcastVersion;
|
||||
import android.nearby.DataElement;
|
||||
import android.nearby.DataElement.DataType;
|
||||
import android.nearby.PresenceBroadcastRequest;
|
||||
import android.nearby.PresenceCredential;
|
||||
import android.nearby.PublicCredential;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.nearby.util.ArrayUtils;
|
||||
import com.android.server.nearby.util.encryption.Cryptor;
|
||||
import com.android.server.nearby.util.encryption.CryptorImpFake;
|
||||
import com.android.server.nearby.util.encryption.CryptorImpIdentityV1;
|
||||
import com.android.server.nearby.util.encryption.CryptorImpV1;
|
||||
import com.android.server.nearby.util.encryption.CryptorMicImp;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -51,37 +56,51 @@ import java.util.Objects;
|
|||
* The header contains:
|
||||
* version (3 bits) | 5 bit reserved for future use (RFU)
|
||||
*/
|
||||
public class ExtendedAdvertisement extends Advertisement{
|
||||
public class ExtendedAdvertisement extends Advertisement {
|
||||
|
||||
public static final int SALT_DATA_LENGTH = 2;
|
||||
|
||||
static final int HEADER_LENGTH = 1;
|
||||
|
||||
static final int IDENTITY_DATA_LENGTH = 16;
|
||||
|
||||
// Identity Index is always 2 .
|
||||
// 0 is reserved, 1 is Salt or Credential Element.
|
||||
private static final int CIPHER_START_INDEX = 2;
|
||||
private final List<DataElement> mDataElements;
|
||||
private final byte[] mKeySeed;
|
||||
|
||||
private final byte[] mAuthenticityKey;
|
||||
private final byte[] mData;
|
||||
|
||||
// All Data Elements including salt and identity.
|
||||
// Each list item (byte array) is a Data Element (with its header).
|
||||
private final List<byte[]> mCompleteDataElementsBytes;
|
||||
// Signature generated from data elements.
|
||||
private final byte[] mHmacTag;
|
||||
private ExtendedAdvertisement(
|
||||
@PresenceCredential.IdentityType int identityType,
|
||||
byte[] identity,
|
||||
byte[] salt,
|
||||
byte[] keySeed,
|
||||
List<Integer> actions,
|
||||
List<DataElement> dataElements) {
|
||||
this.mVersion = PRESENCE_VERSION_V1;
|
||||
this.mIdentityType = identityType;
|
||||
this.mIdentity = identity;
|
||||
this.mSalt = salt;
|
||||
this.mKeySeed = keySeed;
|
||||
this.mDataElements = dataElements;
|
||||
this.mActions = actions;
|
||||
mData = toBytesInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link ExtendedAdvertisement} from a Presence Broadcast Request.
|
||||
*
|
||||
* @return {@link ExtendedAdvertisement} object. {@code null} when the request is illegal.
|
||||
*/
|
||||
@Nullable
|
||||
public static ExtendedAdvertisement createFromRequest(PresenceBroadcastRequest request) {
|
||||
if (request.getVersion() != BroadcastRequest.PRESENCE_VERSION_V1) {
|
||||
if (request.getVersion() != PRESENCE_VERSION_V1) {
|
||||
Log.v(TAG, "ExtendedAdvertisement only supports V1 now.");
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] salt = request.getSalt();
|
||||
if (salt.length != SALT_DATA_LENGTH) {
|
||||
if (salt.length != SALT_DATA_LENGTH && salt.length != ENCRYPTION_INFO_LENGTH - 1) {
|
||||
Log.v(TAG, "Salt does not match correct length");
|
||||
return null;
|
||||
}
|
||||
|
@ -94,12 +113,12 @@ public class ExtendedAdvertisement extends Advertisement{
|
|||
}
|
||||
|
||||
List<Integer> actions = request.getActions();
|
||||
if (actions.isEmpty()) {
|
||||
Log.v(TAG, "ExtendedAdvertisement must contain at least one action");
|
||||
return null;
|
||||
}
|
||||
|
||||
List<DataElement> dataElements = request.getExtendedProperties();
|
||||
// DataElements should include actions.
|
||||
for (int action : actions) {
|
||||
dataElements.add(
|
||||
new DataElement(DataType.ACTION, new byte[]{(byte) action}));
|
||||
}
|
||||
return new ExtendedAdvertisement(
|
||||
request.getCredential().getIdentityType(),
|
||||
identity,
|
||||
|
@ -109,149 +128,252 @@ public class ExtendedAdvertisement extends Advertisement{
|
|||
dataElements);
|
||||
}
|
||||
|
||||
/** Serialize an {@link ExtendedAdvertisement} object into bytes with {@link DataElement}s */
|
||||
@Nullable
|
||||
public byte[] toBytes() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(getLength());
|
||||
|
||||
// Header
|
||||
buffer.put(ExtendedAdvertisementUtils.constructHeader(getVersion()));
|
||||
|
||||
// Salt
|
||||
buffer.put(mCompleteDataElementsBytes.get(0));
|
||||
|
||||
// Identity
|
||||
buffer.put(mCompleteDataElementsBytes.get(1));
|
||||
|
||||
List<Byte> rawDataBytes = new ArrayList<>();
|
||||
// Data Elements (Already includes salt and identity)
|
||||
for (int i = 2; i < mCompleteDataElementsBytes.size(); i++) {
|
||||
byte[] dataElementBytes = mCompleteDataElementsBytes.get(i);
|
||||
for (Byte b : dataElementBytes) {
|
||||
rawDataBytes.add(b);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] dataElements = new byte[rawDataBytes.size()];
|
||||
for (int i = 0; i < rawDataBytes.size(); i++) {
|
||||
dataElements[i] = rawDataBytes.get(i);
|
||||
}
|
||||
|
||||
buffer.put(
|
||||
getCryptor(/* encrypt= */ true).encrypt(dataElements, getSalt(), mAuthenticityKey));
|
||||
|
||||
buffer.put(mHmacTag);
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
/** Deserialize from bytes into an {@link ExtendedAdvertisement} object.
|
||||
* {@code null} when there is something when parsing.
|
||||
/**
|
||||
* Deserialize from bytes into an {@link ExtendedAdvertisement} object.
|
||||
* Return {@code null} when there is an error in parsing.
|
||||
*/
|
||||
@Nullable
|
||||
public static ExtendedAdvertisement fromBytes(byte[] bytes, PublicCredential publicCredential) {
|
||||
@BroadcastRequest.BroadcastVersion
|
||||
public static ExtendedAdvertisement fromBytes(byte[] bytes, PublicCredential sharedCredential) {
|
||||
@BroadcastVersion
|
||||
int version = ExtendedAdvertisementUtils.getVersion(bytes);
|
||||
if (version != PresenceBroadcastRequest.PRESENCE_VERSION_V1) {
|
||||
if (version != PRESENCE_VERSION_V1) {
|
||||
Log.v(TAG, "ExtendedAdvertisement is used in V1 only and version is " + version);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] authenticityKey = publicCredential.getAuthenticityKey();
|
||||
|
||||
int index = HEADER_LENGTH;
|
||||
// Salt
|
||||
byte[] saltHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(bytes, index);
|
||||
DataElementHeader saltHeader = DataElementHeader.fromBytes(version, saltHeaderArray);
|
||||
if (saltHeader == null || saltHeader.getDataType() != DataElement.DataType.SALT) {
|
||||
Log.v(TAG, "First data element has to be salt.");
|
||||
byte[] keySeed = sharedCredential.getAuthenticityKey();
|
||||
byte[] metadataEncryptionKeyUnsignedAdvTag = sharedCredential.getEncryptedMetadataKeyTag();
|
||||
if (keySeed == null || metadataEncryptionKeyUnsignedAdvTag == null) {
|
||||
return null;
|
||||
}
|
||||
index += saltHeaderArray.length;
|
||||
byte[] salt = new byte[saltHeader.getDataLength()];
|
||||
for (int i = 0; i < saltHeader.getDataLength(); i++) {
|
||||
salt[i] = bytes[index++];
|
||||
}
|
||||
|
||||
// Identity
|
||||
int index = 0;
|
||||
// Header
|
||||
byte[] header = new byte[]{bytes[index]};
|
||||
index += HEADER_LENGTH;
|
||||
// Section header
|
||||
byte[] sectionHeader = new byte[]{bytes[index]};
|
||||
index += HEADER_LENGTH;
|
||||
// Salt or Encryption Info
|
||||
byte[] firstHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(bytes, index);
|
||||
DataElementHeader firstHeader = DataElementHeader.fromBytes(version, firstHeaderArray);
|
||||
if (firstHeader == null) {
|
||||
Log.v(TAG, "Cannot find salt.");
|
||||
return null;
|
||||
}
|
||||
@DataType int firstType = firstHeader.getDataType();
|
||||
if (firstType != DataType.SALT && firstType != DataType.ENCRYPTION_INFO) {
|
||||
Log.v(TAG, "First data element has to be Salt or Encryption Info.");
|
||||
return null;
|
||||
}
|
||||
index += firstHeaderArray.length;
|
||||
byte[] firstDeBytes = new byte[firstHeader.getDataLength()];
|
||||
for (int i = 0; i < firstHeader.getDataLength(); i++) {
|
||||
firstDeBytes[i] = bytes[index++];
|
||||
}
|
||||
byte[] nonce = getNonce(firstType, firstDeBytes);
|
||||
if (nonce == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] saltBytes = firstType == DataType.SALT
|
||||
? firstDeBytes : (new EncryptionInfo(firstDeBytes)).getSalt();
|
||||
|
||||
// Identity header
|
||||
byte[] identityHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(bytes, index);
|
||||
DataElementHeader identityHeader =
|
||||
DataElementHeader.fromBytes(version, identityHeaderArray);
|
||||
if (identityHeader == null) {
|
||||
Log.v(TAG, "The second element has to be identity.");
|
||||
if (identityHeader == null || identityHeader.getDataLength() != IDENTITY_DATA_LENGTH) {
|
||||
Log.v(TAG, "The second element has to be a 16-bytes identity.");
|
||||
return null;
|
||||
}
|
||||
index += identityHeaderArray.length;
|
||||
@PresenceCredential.IdentityType int identityType =
|
||||
toPresenceCredentialIdentityType(identityHeader.getDataType());
|
||||
if (identityType == PresenceCredential.IDENTITY_TYPE_UNKNOWN) {
|
||||
Log.v(TAG, "The identity type is unknown.");
|
||||
if (identityType != PresenceCredential.IDENTITY_TYPE_PRIVATE
|
||||
&& identityType != PresenceCredential.IDENTITY_TYPE_TRUSTED) {
|
||||
Log.v(TAG, "Only supports encrypted advertisement.");
|
||||
return null;
|
||||
}
|
||||
byte[] encryptedIdentity = new byte[identityHeader.getDataLength()];
|
||||
for (int i = 0; i < identityHeader.getDataLength(); i++) {
|
||||
encryptedIdentity[i] = bytes[index++];
|
||||
}
|
||||
byte[] identity =
|
||||
CryptorImpIdentityV1
|
||||
.getInstance().decrypt(encryptedIdentity, salt, authenticityKey);
|
||||
|
||||
Cryptor cryptor = getCryptor(/* encrypt= */ true);
|
||||
byte[] encryptedDataElements =
|
||||
new byte[bytes.length - index - cryptor.getSignatureLength()];
|
||||
// Decrypt other data elements
|
||||
System.arraycopy(bytes, index, encryptedDataElements, 0, encryptedDataElements.length);
|
||||
byte[] decryptedDataElements =
|
||||
cryptor.decrypt(encryptedDataElements, salt, authenticityKey);
|
||||
if (decryptedDataElements == null) {
|
||||
// Ciphertext
|
||||
Cryptor cryptor = CryptorMicImp.getInstance();
|
||||
byte[] ciphertext = new byte[bytes.length - index - cryptor.getSignatureLength()];
|
||||
System.arraycopy(bytes, index, ciphertext, 0, ciphertext.length);
|
||||
byte[] plaintext = cryptor.decrypt(ciphertext, nonce, keySeed);
|
||||
if (plaintext == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Verification
|
||||
// Verify the computed metadata encryption key tag
|
||||
// First 16 bytes is metadata encryption key data
|
||||
byte[] metadataEncryptionKey = new byte[IDENTITY_DATA_LENGTH];
|
||||
System.arraycopy(plaintext, 0, metadataEncryptionKey, 0, IDENTITY_DATA_LENGTH);
|
||||
// Verify metadata encryption key tag
|
||||
byte[] computedMetadataEncryptionKeyTag =
|
||||
CryptorMicImp.generateMetadataEncryptionKeyTag(metadataEncryptionKey,
|
||||
keySeed);
|
||||
if (!Arrays.equals(computedMetadataEncryptionKeyTag, metadataEncryptionKeyUnsignedAdvTag)) {
|
||||
Log.w(TAG,
|
||||
"The calculated metadata encryption key tag is different from the metadata "
|
||||
+ "encryption key unsigned adv tag in the SharedCredential.");
|
||||
return null;
|
||||
}
|
||||
// Verify the computed HMAC tag is equal to HMAC tag in advertisement
|
||||
if (cryptor.getSignatureLength() > 0) {
|
||||
byte[] expectedHmacTag = new byte[cryptor.getSignatureLength()];
|
||||
System.arraycopy(
|
||||
bytes, bytes.length - cryptor.getSignatureLength(),
|
||||
expectedHmacTag, 0, cryptor.getSignatureLength());
|
||||
if (!cryptor.verify(decryptedDataElements, authenticityKey, expectedHmacTag)) {
|
||||
Log.e(TAG, "HMAC tags not match.");
|
||||
return null;
|
||||
}
|
||||
byte[] expectedHmacTag = new byte[cryptor.getSignatureLength()];
|
||||
System.arraycopy(
|
||||
bytes, bytes.length - cryptor.getSignatureLength(),
|
||||
expectedHmacTag, 0, cryptor.getSignatureLength());
|
||||
byte[] micInput = ArrayUtils.concatByteArrays(
|
||||
PRESENCE_UUID_BYTES, header, sectionHeader,
|
||||
firstHeaderArray, firstDeBytes,
|
||||
nonce, identityHeaderArray, ciphertext);
|
||||
if (!cryptor.verify(micInput, keySeed, expectedHmacTag)) {
|
||||
Log.e(TAG, "HMAC tag not match.");
|
||||
return null;
|
||||
}
|
||||
|
||||
int dataElementArrayIndex = 0;
|
||||
// Other Data Elements
|
||||
List<Integer> actions = new ArrayList<>();
|
||||
List<DataElement> dataElements = new ArrayList<>();
|
||||
while (dataElementArrayIndex < decryptedDataElements.length) {
|
||||
byte[] deHeaderArray = ExtendedAdvertisementUtils
|
||||
.getDataElementHeader(decryptedDataElements, dataElementArrayIndex);
|
||||
DataElementHeader deHeader = DataElementHeader.fromBytes(version, deHeaderArray);
|
||||
dataElementArrayIndex += deHeaderArray.length;
|
||||
byte[] otherDataElements = new byte[plaintext.length - IDENTITY_DATA_LENGTH];
|
||||
System.arraycopy(plaintext, IDENTITY_DATA_LENGTH,
|
||||
otherDataElements, 0, otherDataElements.length);
|
||||
List<DataElement> dataElements = getDataElementsFromBytes(version, otherDataElements);
|
||||
if (dataElements.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
List<Integer> actions = getActionsFromDataElements(dataElements);
|
||||
if (actions == null) {
|
||||
return null;
|
||||
}
|
||||
return new ExtendedAdvertisement(identityType, metadataEncryptionKey, saltBytes, keySeed,
|
||||
actions, dataElements);
|
||||
}
|
||||
|
||||
@DataElement.DataType int type = Objects.requireNonNull(deHeader).getDataType();
|
||||
if (type == DataElement.DataType.ACTION) {
|
||||
if (deHeader.getDataLength() != 1) {
|
||||
Log.v(TAG, "Action id should only 1 byte.");
|
||||
@PresenceCredential.IdentityType
|
||||
private static int toPresenceCredentialIdentityType(@DataType int type) {
|
||||
switch (type) {
|
||||
case DataType.PRIVATE_IDENTITY:
|
||||
return PresenceCredential.IDENTITY_TYPE_PRIVATE;
|
||||
case DataType.PROVISIONED_IDENTITY:
|
||||
return PresenceCredential.IDENTITY_TYPE_PROVISIONED;
|
||||
case DataType.TRUSTED_IDENTITY:
|
||||
return PresenceCredential.IDENTITY_TYPE_TRUSTED;
|
||||
case DataType.PUBLIC_IDENTITY:
|
||||
default:
|
||||
return PresenceCredential.IDENTITY_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@DataType
|
||||
private static int toDataType(@PresenceCredential.IdentityType int identityType) {
|
||||
switch (identityType) {
|
||||
case PresenceCredential.IDENTITY_TYPE_PRIVATE:
|
||||
return DataType.PRIVATE_IDENTITY;
|
||||
case PresenceCredential.IDENTITY_TYPE_PROVISIONED:
|
||||
return DataType.PROVISIONED_IDENTITY;
|
||||
case PresenceCredential.IDENTITY_TYPE_TRUSTED:
|
||||
return DataType.TRUSTED_IDENTITY;
|
||||
case PresenceCredential.IDENTITY_TYPE_UNKNOWN:
|
||||
default:
|
||||
return DataType.PUBLIC_IDENTITY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given {@link DataType} is salt, or one of the
|
||||
* identities. Identities should be able to convert to {@link PresenceCredential.IdentityType}s.
|
||||
*/
|
||||
private static boolean isSaltOrIdentity(@DataType int type) {
|
||||
return type == DataType.SALT || type == DataType.ENCRYPTION_INFO
|
||||
|| type == DataType.PRIVATE_IDENTITY
|
||||
|| type == DataType.TRUSTED_IDENTITY
|
||||
|| type == DataType.PROVISIONED_IDENTITY
|
||||
|| type == DataType.PUBLIC_IDENTITY;
|
||||
}
|
||||
|
||||
/** Serialize an {@link ExtendedAdvertisement} object into bytes with {@link DataElement}s */
|
||||
@Nullable
|
||||
public byte[] toBytes() {
|
||||
return mData.clone();
|
||||
}
|
||||
|
||||
/** Serialize an {@link ExtendedAdvertisement} object into bytes with {@link DataElement}s */
|
||||
@Nullable
|
||||
public byte[] toBytesInternal() {
|
||||
int sectionLength = 0;
|
||||
// Salt
|
||||
DataElement saltDe;
|
||||
byte[] nonce;
|
||||
try {
|
||||
switch (mSalt.length) {
|
||||
case SALT_DATA_LENGTH:
|
||||
saltDe = new DataElement(DataType.SALT, mSalt);
|
||||
nonce = CryptorMicImp.generateAdvNonce(mSalt);
|
||||
break;
|
||||
case ENCRYPTION_INFO_LENGTH - 1:
|
||||
saltDe = new DataElement(DataType.ENCRYPTION_INFO,
|
||||
EncryptionInfo.toByte(EncryptionInfo.EncodingScheme.MIC, mSalt));
|
||||
nonce = CryptorMicImp.generateAdvNonce(mSalt, CIPHER_START_INDEX);
|
||||
break;
|
||||
default:
|
||||
Log.w(TAG, "Invalid salt size.");
|
||||
return null;
|
||||
}
|
||||
actions.add((int) decryptedDataElements[dataElementArrayIndex++]);
|
||||
} else {
|
||||
if (isSaltOrIdentity(type)) {
|
||||
Log.v(TAG, "Type " + type + " is duplicated. There should be only one salt"
|
||||
+ " and one identity in the advertisement.");
|
||||
return null;
|
||||
}
|
||||
byte[] deData = new byte[deHeader.getDataLength()];
|
||||
for (int i = 0; i < deHeader.getDataLength(); i++) {
|
||||
deData[i] = decryptedDataElements[dataElementArrayIndex++];
|
||||
}
|
||||
dataElements.add(new DataElement(type, deData));
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
Log.w(TAG, "Failed to generate the IV for encryption.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ExtendedAdvertisement(identityType, identity, salt, authenticityKey, actions,
|
||||
dataElements);
|
||||
byte[] saltOrEncryptionInfoBytes =
|
||||
ExtendedAdvertisementUtils.convertDataElementToBytes(saltDe);
|
||||
sectionLength += saltOrEncryptionInfoBytes.length;
|
||||
// 16 bytes encrypted identity
|
||||
@DataType int identityDataType = toDataType(getIdentityType());
|
||||
byte[] identityHeaderBytes = new DataElementHeader(PRESENCE_VERSION_V1,
|
||||
identityDataType, mIdentity.length).toBytes();
|
||||
sectionLength += identityHeaderBytes.length;
|
||||
final List<DataElement> dataElementList = getDataElements();
|
||||
byte[] ciphertext = getCiphertext(nonce, dataElementList);
|
||||
if (ciphertext == null) {
|
||||
return null;
|
||||
}
|
||||
sectionLength += ciphertext.length;
|
||||
// mic
|
||||
sectionLength += CryptorMicImp.MIC_LENGTH;
|
||||
mLength = sectionLength;
|
||||
// header
|
||||
byte header = ExtendedAdvertisementUtils.constructHeader(getVersion());
|
||||
mLength += HEADER_LENGTH;
|
||||
// section header
|
||||
if (sectionLength > 255) {
|
||||
Log.e(TAG, "A section should be shorter than 255 bytes.");
|
||||
return null;
|
||||
}
|
||||
byte sectionHeader = (byte) sectionLength;
|
||||
mLength += HEADER_LENGTH;
|
||||
|
||||
// generates mic
|
||||
ByteBuffer micInputBuffer = ByteBuffer.allocate(
|
||||
mLength + PRESENCE_UUID_BYTES.length + nonce.length - CryptorMicImp.MIC_LENGTH);
|
||||
micInputBuffer.put(PRESENCE_UUID_BYTES);
|
||||
micInputBuffer.put(header);
|
||||
micInputBuffer.put(sectionHeader);
|
||||
micInputBuffer.put(saltOrEncryptionInfoBytes);
|
||||
micInputBuffer.put(nonce);
|
||||
micInputBuffer.put(identityHeaderBytes);
|
||||
micInputBuffer.put(ciphertext);
|
||||
byte[] micInput = micInputBuffer.array();
|
||||
byte[] mic = CryptorMicImp.getInstance().sign(micInput, mKeySeed);
|
||||
if (mic == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(mLength);
|
||||
buffer.put(header);
|
||||
buffer.put(sectionHeader);
|
||||
buffer.put(saltOrEncryptionInfoBytes);
|
||||
buffer.put(identityHeaderBytes);
|
||||
buffer.put(ciphertext);
|
||||
buffer.put(mic);
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
/** Returns the {@link DataElement}s in the advertisement. */
|
||||
|
@ -260,7 +382,7 @@ public class ExtendedAdvertisement extends Advertisement{
|
|||
}
|
||||
|
||||
/** Returns the {@link DataElement}s in the advertisement according to the key. */
|
||||
public List<DataElement> getDataElements(@DataElement.DataType int key) {
|
||||
public List<DataElement> getDataElements(@DataType int key) {
|
||||
List<DataElement> res = new ArrayList<>();
|
||||
for (DataElement dataElement : mDataElements) {
|
||||
if (key == dataElement.getKey()) {
|
||||
|
@ -285,125 +407,86 @@ public class ExtendedAdvertisement extends Advertisement{
|
|||
getActions());
|
||||
}
|
||||
|
||||
ExtendedAdvertisement(
|
||||
@PresenceCredential.IdentityType int identityType,
|
||||
byte[] identity,
|
||||
byte[] salt,
|
||||
byte[] authenticityKey,
|
||||
List<Integer> actions,
|
||||
List<DataElement> dataElements) {
|
||||
this.mVersion = BroadcastRequest.PRESENCE_VERSION_V1;
|
||||
this.mIdentityType = identityType;
|
||||
this.mIdentity = identity;
|
||||
this.mSalt = salt;
|
||||
this.mAuthenticityKey = authenticityKey;
|
||||
this.mActions = actions;
|
||||
this.mDataElements = dataElements;
|
||||
this.mCompleteDataElementsBytes = new ArrayList<>();
|
||||
@Nullable
|
||||
private byte[] getCiphertext(byte[] nonce, List<DataElement> dataElements) {
|
||||
Cryptor cryptor = CryptorMicImp.getInstance();
|
||||
byte[] rawDeBytes = mIdentity;
|
||||
for (DataElement dataElement : dataElements) {
|
||||
rawDeBytes = ArrayUtils.concatByteArrays(rawDeBytes,
|
||||
ExtendedAdvertisementUtils.convertDataElementToBytes(dataElement));
|
||||
}
|
||||
return cryptor.encrypt(rawDeBytes, nonce, mKeySeed);
|
||||
}
|
||||
|
||||
int length = HEADER_LENGTH; // header
|
||||
private static List<DataElement> getDataElementsFromBytes(
|
||||
@BroadcastVersion int version, byte[] bytes) {
|
||||
List<DataElement> res = new ArrayList<>();
|
||||
if (ArrayUtils.isEmpty(bytes)) {
|
||||
return res;
|
||||
}
|
||||
int index = 0;
|
||||
while (index < bytes.length) {
|
||||
byte[] deHeaderArray = ExtendedAdvertisementUtils
|
||||
.getDataElementHeader(bytes, index);
|
||||
DataElementHeader deHeader = DataElementHeader.fromBytes(version, deHeaderArray);
|
||||
index += deHeaderArray.length;
|
||||
@DataType int type = Objects.requireNonNull(deHeader).getDataType();
|
||||
if (isSaltOrIdentity(type)) {
|
||||
Log.v(TAG, "Type " + type + " is duplicated. There should be only one salt"
|
||||
+ " and one identity in the advertisement.");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
byte[] deData = new byte[deHeader.getDataLength()];
|
||||
for (int i = 0; i < deHeader.getDataLength(); i++) {
|
||||
deData[i] = bytes[index++];
|
||||
}
|
||||
res.add(new DataElement(type, deData));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Salt
|
||||
DataElement saltElement = new DataElement(DataElement.DataType.SALT, salt);
|
||||
byte[] saltByteArray = ExtendedAdvertisementUtils.convertDataElementToBytes(saltElement);
|
||||
mCompleteDataElementsBytes.add(saltByteArray);
|
||||
length += saltByteArray.length;
|
||||
@Nullable
|
||||
private static byte[] getNonce(@DataType int type, byte[] data) {
|
||||
try {
|
||||
if (type == DataType.SALT) {
|
||||
if (data.length != SALT_DATA_LENGTH) {
|
||||
Log.v(TAG, "Salt DataElement needs to be 2 bytes.");
|
||||
return null;
|
||||
}
|
||||
return CryptorMicImp.generateAdvNonce(data);
|
||||
} else if (type == DataType.ENCRYPTION_INFO) {
|
||||
try {
|
||||
EncryptionInfo info = new EncryptionInfo(data);
|
||||
if (info.getEncodingScheme() != EncryptionInfo.EncodingScheme.MIC) {
|
||||
Log.v(TAG, "Not support Signature yet.");
|
||||
return null;
|
||||
}
|
||||
return CryptorMicImp.generateAdvNonce(info.getSalt(), CIPHER_START_INDEX);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, "Salt DataElement needs to be 17 bytes.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
Log.w(TAG, "Failed to decrypt metadata encryption key.", e);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Identity
|
||||
byte[] encryptedIdentity =
|
||||
CryptorImpIdentityV1.getInstance().encrypt(identity, salt, authenticityKey);
|
||||
DataElement identityElement = new DataElement(toDataType(identityType), encryptedIdentity);
|
||||
byte[] identityByteArray =
|
||||
ExtendedAdvertisementUtils.convertDataElementToBytes(identityElement);
|
||||
mCompleteDataElementsBytes.add(identityByteArray);
|
||||
length += identityByteArray.length;
|
||||
|
||||
List<Byte> dataElementBytes = new ArrayList<>();
|
||||
// Intents
|
||||
for (int action : mActions) {
|
||||
DataElement actionElement = new DataElement(DataElement.DataType.ACTION,
|
||||
new byte[] {(byte) action});
|
||||
byte[] intentByteArray =
|
||||
ExtendedAdvertisementUtils.convertDataElementToBytes(actionElement);
|
||||
mCompleteDataElementsBytes.add(intentByteArray);
|
||||
for (Byte b : intentByteArray) {
|
||||
dataElementBytes.add(b);
|
||||
@Nullable
|
||||
private static List<Integer> getActionsFromDataElements(List<DataElement> dataElements) {
|
||||
List<Integer> actions = new ArrayList<>();
|
||||
for (DataElement dataElement : dataElements) {
|
||||
if (dataElement.getKey() == DataElement.DataType.ACTION) {
|
||||
byte[] value = dataElement.getValue();
|
||||
if (value.length != 1) {
|
||||
Log.w(TAG, "Action should be only 1 byte.");
|
||||
return null;
|
||||
}
|
||||
actions.add(Byte.toUnsignedInt(value[0]));
|
||||
}
|
||||
}
|
||||
|
||||
// Data Elements (Extended properties)
|
||||
for (DataElement dataElement : mDataElements) {
|
||||
byte[] deByteArray = ExtendedAdvertisementUtils.convertDataElementToBytes(dataElement);
|
||||
mCompleteDataElementsBytes.add(deByteArray);
|
||||
for (Byte b : deByteArray) {
|
||||
dataElementBytes.add(b);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] data = new byte[dataElementBytes.size()];
|
||||
for (int i = 0; i < dataElementBytes.size(); i++) {
|
||||
data[i] = dataElementBytes.get(i);
|
||||
}
|
||||
Cryptor cryptor = getCryptor(/* encrypt= */ true);
|
||||
byte[] encryptedDeBytes = cryptor.encrypt(data, salt, authenticityKey);
|
||||
|
||||
length += encryptedDeBytes.length;
|
||||
|
||||
// Signature
|
||||
byte[] hmacTag = Objects.requireNonNull(cryptor.sign(data, authenticityKey));
|
||||
mHmacTag = hmacTag;
|
||||
length += hmacTag.length;
|
||||
|
||||
this.mLength = length;
|
||||
}
|
||||
|
||||
@PresenceCredential.IdentityType
|
||||
private static int toPresenceCredentialIdentityType(@DataElement.DataType int type) {
|
||||
switch (type) {
|
||||
case DataElement.DataType.PRIVATE_IDENTITY:
|
||||
return PresenceCredential.IDENTITY_TYPE_PRIVATE;
|
||||
case DataElement.DataType.PROVISIONED_IDENTITY:
|
||||
return PresenceCredential.IDENTITY_TYPE_PROVISIONED;
|
||||
case DataElement.DataType.TRUSTED_IDENTITY:
|
||||
return PresenceCredential.IDENTITY_TYPE_TRUSTED;
|
||||
case DataElement.DataType.PUBLIC_IDENTITY:
|
||||
default:
|
||||
return PresenceCredential.IDENTITY_TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@DataElement.DataType
|
||||
private static int toDataType(@PresenceCredential.IdentityType int identityType) {
|
||||
switch (identityType) {
|
||||
case PresenceCredential.IDENTITY_TYPE_PRIVATE:
|
||||
return DataElement.DataType.PRIVATE_IDENTITY;
|
||||
case PresenceCredential.IDENTITY_TYPE_PROVISIONED:
|
||||
return DataElement.DataType.PROVISIONED_IDENTITY;
|
||||
case PresenceCredential.IDENTITY_TYPE_TRUSTED:
|
||||
return DataElement.DataType.TRUSTED_IDENTITY;
|
||||
case PresenceCredential.IDENTITY_TYPE_UNKNOWN:
|
||||
default:
|
||||
return DataElement.DataType.PUBLIC_IDENTITY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the given {@link DataElement.DataType} is salt, or one of the
|
||||
* identities. Identities should be able to convert to {@link PresenceCredential.IdentityType}s.
|
||||
*/
|
||||
private static boolean isSaltOrIdentity(@DataElement.DataType int type) {
|
||||
return type == DataElement.DataType.SALT || type == DataElement.DataType.PRIVATE_IDENTITY
|
||||
|| type == DataElement.DataType.TRUSTED_IDENTITY
|
||||
|| type == DataElement.DataType.PROVISIONED_IDENTITY
|
||||
|| type == DataElement.DataType.PUBLIC_IDENTITY;
|
||||
}
|
||||
|
||||
private static Cryptor getCryptor(boolean encrypt) {
|
||||
if (encrypt) {
|
||||
Log.d(TAG, "get V1 Cryptor");
|
||||
return CryptorImpV1.getInstance();
|
||||
}
|
||||
Log.d(TAG, "get fake Cryptor");
|
||||
return CryptorImpFake.getInstance();
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,16 @@ package com.android.server.nearby.presence;
|
|||
|
||||
import static com.android.server.nearby.common.bluetooth.fastpair.BluetoothUuids.to128BitUuid;
|
||||
|
||||
import com.android.server.nearby.util.ArrayUtils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Constants for Nearby Presence operations.
|
||||
*/
|
||||
public class PresenceConstants {
|
||||
/** The Presence UUID value in byte array format. */
|
||||
public static final byte[] PRESENCE_UUID_BYTES = ArrayUtils.intToByteArray(0xFCF1);
|
||||
|
||||
/** Presence advertisement service data uuid. */
|
||||
public static final UUID PRESENCE_UUID = to128BitUuid((short) 0xFCF1);
|
||||
|
|
|
@ -46,7 +46,6 @@ import com.android.server.nearby.presence.ExtendedAdvertisement;
|
|||
import com.android.server.nearby.presence.PresenceConstants;
|
||||
import com.android.server.nearby.util.ArrayUtils;
|
||||
import com.android.server.nearby.util.ForegroundThread;
|
||||
import com.android.server.nearby.util.encryption.CryptorImpIdentityV1;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
|
@ -143,10 +142,6 @@ public class BleDiscoveryProvider extends AbstractDiscoveryProvider {
|
|||
.addMedium(NearbyDevice.Medium.BLE)
|
||||
.setName(deviceName)
|
||||
.setRssi(rssi);
|
||||
for (int i : advertisement.getActions()) {
|
||||
builder.addExtendedProperty(new DataElement(DataElement.DataType.ACTION,
|
||||
new byte[]{(byte) i}));
|
||||
}
|
||||
for (DataElement dataElement : advertisement.getDataElements()) {
|
||||
builder.addExtendedProperty(dataElement);
|
||||
}
|
||||
|
@ -298,17 +293,13 @@ public class BleDiscoveryProvider extends AbstractDiscoveryProvider {
|
|||
if (advertisement == null) {
|
||||
continue;
|
||||
}
|
||||
if (CryptorImpIdentityV1.getInstance().verify(
|
||||
advertisement.getIdentity(),
|
||||
credential.getEncryptedMetadataKeyTag())) {
|
||||
builder.setPresenceDevice(getPresenceDevice(advertisement, deviceName,
|
||||
rssi));
|
||||
builder.setEncryptionKeyTag(credential.getEncryptedMetadataKeyTag());
|
||||
if (!ArrayUtils.isEmpty(credential.getSecretId())) {
|
||||
builder.setDeviceId(Arrays.hashCode(credential.getSecretId()));
|
||||
}
|
||||
return;
|
||||
builder.setPresenceDevice(getPresenceDevice(advertisement, deviceName,
|
||||
rssi));
|
||||
builder.setEncryptionKeyTag(credential.getEncryptedMetadataKeyTag());
|
||||
if (!ArrayUtils.isEmpty(credential.getSecretId())) {
|
||||
builder.setDeviceId(Arrays.hashCode(credential.getSecretId()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ import java.util.Arrays;
|
|||
* ArrayUtils class that help manipulate array.
|
||||
*/
|
||||
public class ArrayUtils {
|
||||
private static final char[] HEX_UPPERCASE = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
|
||||
};
|
||||
|
||||
/** Concatenate N arrays of bytes into a single array. */
|
||||
public static byte[] concatByteArrays(byte[]... arrays) {
|
||||
// Degenerate case - no input provided.
|
||||
|
@ -52,4 +56,67 @@ public class ArrayUtils {
|
|||
public static boolean isEmpty(byte[] bytes) {
|
||||
return bytes == null || bytes.length == 0;
|
||||
}
|
||||
|
||||
/** Appends a byte array to a byte. */
|
||||
public static byte[] append(byte a, byte[] b) {
|
||||
if (b == null) {
|
||||
return new byte[]{a};
|
||||
}
|
||||
|
||||
int length = b.length;
|
||||
byte[] result = new byte[length + 1];
|
||||
result[0] = a;
|
||||
System.arraycopy(b, 0, result, 1, length);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Integer to a 2-byte array.
|
||||
*/
|
||||
public static byte[] intToByteArray(int value) {
|
||||
return new byte[] {(byte) (value >> 8), (byte) value};
|
||||
}
|
||||
|
||||
/** Appends a byte to a byte array. */
|
||||
public static byte[] append(byte[] a, byte b) {
|
||||
if (a == null) {
|
||||
return new byte[]{b};
|
||||
}
|
||||
|
||||
int length = a.length;
|
||||
byte[] result = new byte[length + 1];
|
||||
System.arraycopy(a, 0, result, 0, length);
|
||||
result[length] = b;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Convert an hex string to a byte array. */
|
||||
|
||||
public static byte[] stringToBytes(String hex) throws IllegalArgumentException {
|
||||
int length = hex.length();
|
||||
if (length % 2 != 0) {
|
||||
throw new IllegalArgumentException("Hex string has odd number of characters");
|
||||
}
|
||||
byte[] out = new byte[length / 2];
|
||||
for (int i = 0; i < length; i += 2) {
|
||||
// Byte.parseByte() doesn't work here because it expects a hex value in -128, 127, and
|
||||
// our hex values are in 0, 255.
|
||||
out[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Encodes a byte array as a hexadecimal representation of bytes. */
|
||||
public static String bytesToStringUppercase(byte[] bytes) {
|
||||
int length = bytes.length;
|
||||
StringBuilder out = new StringBuilder(length * 2);
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (i == length - 1 && (bytes[i] & 0xff) == 0) {
|
||||
break;
|
||||
}
|
||||
out.append(HEX_UPPERCASE[(bytes[i] & 0xf0) >>> 4]);
|
||||
out.append(HEX_UPPERCASE[bytes[i] & 0x0f]);
|
||||
}
|
||||
return out.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ import android.util.Log;
|
|||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.common.hash.Hashing;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
|
@ -30,22 +34,28 @@ import javax.crypto.spec.SecretKeySpec;
|
|||
|
||||
/** Class for encryption/decryption functionality. */
|
||||
public abstract class Cryptor {
|
||||
/**
|
||||
* In the form of "algorithm/mode/padding". Must be the same across broadcast and scan devices.
|
||||
*/
|
||||
public static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding";
|
||||
|
||||
public static final byte[] NP_HKDF_SALT = "Google Nearby".getBytes(StandardCharsets.US_ASCII);
|
||||
|
||||
/** AES only supports key sizes of 16, 24 or 32 bytes. */
|
||||
static final int AUTHENTICITY_KEY_BYTE_SIZE = 16;
|
||||
|
||||
private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
|
||||
public static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
|
||||
|
||||
/**
|
||||
* Encrypt the provided data blob.
|
||||
*
|
||||
* @param data data blob to be encrypted.
|
||||
* @param salt used for IV
|
||||
* @param iv advertisement nonce
|
||||
* @param secretKeyBytes secrete key accessed from credentials
|
||||
* @return encrypted data, {@code null} if failed to encrypt.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] encrypt(byte[] data, byte[] salt, byte[] secretKeyBytes) {
|
||||
public byte[] encrypt(byte[] data, byte[] iv, byte[] secretKeyBytes) {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -53,12 +63,12 @@ public abstract class Cryptor {
|
|||
* Decrypt the original data blob from the provided byte array.
|
||||
*
|
||||
* @param encryptedData data blob to be decrypted.
|
||||
* @param salt used for IV
|
||||
* @param iv advertisement nonce
|
||||
* @param secretKeyBytes secrete key accessed from credentials
|
||||
* @return decrypted data, {@code null} if failed to decrypt.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] secretKeyBytes) {
|
||||
public byte[] decrypt(byte[] encryptedData, byte[] iv, byte[] secretKeyBytes) {
|
||||
return encryptedData;
|
||||
}
|
||||
|
||||
|
@ -90,7 +100,7 @@ public abstract class Cryptor {
|
|||
* A HAMC sha256 based HKDF algorithm to pseudo randomly hash data and salt into a byte array of
|
||||
* given size.
|
||||
*/
|
||||
// Based on google3/third_party/tink/java/src/main/java/com/google/crypto/tink/subtle/Hkdf.java
|
||||
// Based on crypto/tink/subtle/Hkdf.java
|
||||
@Nullable
|
||||
static byte[] computeHkdf(byte[] ikm, byte[] salt, int size) {
|
||||
Mac mac;
|
||||
|
@ -146,4 +156,66 @@ public abstract class Cryptor {
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes an HKDF.
|
||||
*
|
||||
* @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or
|
||||
* "HMACSHA256".
|
||||
* @param ikm the input keying material.
|
||||
* @param salt optional salt. A possibly non-secret random value.
|
||||
* (If no salt is provided i.e. if salt has length 0)
|
||||
* then an array of 0s of the same size as the hash
|
||||
* digest is used as salt.
|
||||
* @param info optional context and application specific information.
|
||||
* @param size The length of the generated pseudorandom string in bytes.
|
||||
* @throws GeneralSecurityException if the {@code macAlgorithm} is not supported or if {@code
|
||||
* size} is too large or if {@code salt} is not a valid key for
|
||||
* macAlgorithm (which should not
|
||||
* happen since HMAC allows key sizes up to 2^64).
|
||||
*/
|
||||
public static byte[] computeHkdf(
|
||||
String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size)
|
||||
throws GeneralSecurityException {
|
||||
Mac mac = Mac.getInstance(macAlgorithm);
|
||||
if (size > 255 * mac.getMacLength()) {
|
||||
throw new GeneralSecurityException("size too large");
|
||||
}
|
||||
if (salt == null || salt.length == 0) {
|
||||
// According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
|
||||
// then HKDF uses a salt that is an array of zeros of the same length as the hash
|
||||
// digest.
|
||||
mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
|
||||
} else {
|
||||
mac.init(new SecretKeySpec(salt, macAlgorithm));
|
||||
}
|
||||
byte[] prk = mac.doFinal(ikm);
|
||||
byte[] result = new byte[size];
|
||||
int ctr = 1;
|
||||
int pos = 0;
|
||||
mac.init(new SecretKeySpec(prk, macAlgorithm));
|
||||
byte[] digest = new byte[0];
|
||||
while (true) {
|
||||
mac.update(digest);
|
||||
mac.update(info);
|
||||
mac.update((byte) ctr);
|
||||
digest = mac.doFinal();
|
||||
if (pos + digest.length < size) {
|
||||
System.arraycopy(digest, 0, result, pos, digest.length);
|
||||
pos += digest.length;
|
||||
ctr++;
|
||||
} else {
|
||||
System.arraycopy(digest, 0, result, pos, size - pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Generates the HMAC bytes. */
|
||||
public static byte[] generateHmac(String algorithm, byte[] input, byte[] key) {
|
||||
return Hashing.hmacSha256(new SecretKeySpec(key, algorithm))
|
||||
.hashBytes(input)
|
||||
.asBytes();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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.util.encryption;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* A Cryptor that returns the original data without actual encryption
|
||||
*/
|
||||
public class CryptorImpFake extends Cryptor {
|
||||
// Lazily instantiated when {@link #getInstance()} is called.
|
||||
@Nullable
|
||||
private static CryptorImpFake sCryptor;
|
||||
|
||||
/** Returns an instance of CryptorImpFake. */
|
||||
public static CryptorImpFake getInstance() {
|
||||
if (sCryptor == null) {
|
||||
sCryptor = new CryptorImpFake();
|
||||
}
|
||||
return sCryptor;
|
||||
}
|
||||
|
||||
private CryptorImpFake() {
|
||||
}
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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.util.encryption;
|
||||
|
||||
import static com.android.server.nearby.NearbyService.TAG;
|
||||
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* {@link android.nearby.BroadcastRequest#PRESENCE_VERSION_V1} for identity
|
||||
* encryption and decryption.
|
||||
*/
|
||||
public class CryptorImpIdentityV1 extends Cryptor {
|
||||
|
||||
// 3 16 byte arrays known by both the encryptor and decryptor.
|
||||
private static final byte[] EK_IV =
|
||||
new byte[] {14, -123, -39, 42, 109, 127, 83, 27, 27, 11, 91, -38, 92, 17, -84, 66};
|
||||
private static final byte[] ESALT_IV =
|
||||
new byte[] {46, 83, -19, 10, -127, -31, -31, 12, 31, 76, 63, -9, 33, -66, 15, -10};
|
||||
private static final byte[] KTAG_IV =
|
||||
{-22, -83, -6, 67, 16, -99, -13, -9, 8, -3, -16, 37, -75, 47, 1, -56};
|
||||
|
||||
/** Length of encryption key required by AES/GCM encryption. */
|
||||
private static final int ENCRYPTION_KEY_SIZE = 32;
|
||||
|
||||
/** Length of salt required by AES/GCM encryption. */
|
||||
private static final int AES_CTR_IV_SIZE = 16;
|
||||
|
||||
/** Length HMAC tag */
|
||||
public static final int HMAC_TAG_SIZE = 8;
|
||||
|
||||
/**
|
||||
* In the form of "algorithm/mode/padding". Must be the same across broadcast and scan devices.
|
||||
*/
|
||||
private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String ENCRYPT_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
|
||||
|
||||
// Lazily instantiated when {@link #getInstance()} is called.
|
||||
@Nullable private static CryptorImpIdentityV1 sCryptor;
|
||||
|
||||
/** Returns an instance of CryptorImpIdentityV1. */
|
||||
public static CryptorImpIdentityV1 getInstance() {
|
||||
if (sCryptor == null) {
|
||||
sCryptor = new CryptorImpIdentityV1();
|
||||
}
|
||||
return sCryptor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] encrypt(byte[] data, byte[] salt, byte[] authenticityKey) {
|
||||
if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
|
||||
Log.w(TAG, "Illegal authenticity key size");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generates a 32 bytes encryption key from authenticity_key
|
||||
byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, EK_IV, ENCRYPTION_KEY_SIZE);
|
||||
if (encryptionKey == null) {
|
||||
Log.e(TAG, "Failed to generate encryption key.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Encrypts the data using the encryption key
|
||||
SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
Log.e(TAG, "Failed to encrypt with secret key.", e);
|
||||
return null;
|
||||
}
|
||||
byte[] esalt = Cryptor.computeHkdf(salt, ESALT_IV, AES_CTR_IV_SIZE);
|
||||
if (esalt == null) {
|
||||
Log.e(TAG, "Failed to generate salt.");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(esalt));
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
Log.e(TAG, "Failed to initialize cipher.", e);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return cipher.doFinal(data);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||
Log.e(TAG, "Failed to encrypt with secret key.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] authenticityKey) {
|
||||
if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
|
||||
Log.w(TAG, "Illegal authenticity key size");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generates a 32 bytes encryption key from authenticity_key
|
||||
byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, EK_IV, ENCRYPTION_KEY_SIZE);
|
||||
if (encryptionKey == null) {
|
||||
Log.e(TAG, "Failed to generate encryption key.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Decrypts the data using the encryption key
|
||||
SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
Log.e(TAG, "Failed to get cipher instance.", e);
|
||||
return null;
|
||||
}
|
||||
byte[] esalt = Cryptor.computeHkdf(salt, ESALT_IV, AES_CTR_IV_SIZE);
|
||||
if (esalt == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(esalt));
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
Log.e(TAG, "Failed to initialize cipher.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return cipher.doFinal(encryptedData);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||
Log.e(TAG, "Failed to decrypt bytes with secret key.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a digital signature for the data.
|
||||
*
|
||||
* @return signature {@code null} if failed to sign
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] sign(byte[] data, byte[] salt) {
|
||||
if (data == null) {
|
||||
Log.e(TAG, "Not generate HMAC tag because of invalid data input.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generates a 8 bytes HMAC tag
|
||||
return Cryptor.computeHkdf(data, salt, HMAC_TAG_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a digital signature for the data.
|
||||
* Uses KTAG_IV as salt value.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] sign(byte[] data) {
|
||||
// Generates a 8 bytes HMAC tag
|
||||
return sign(data, KTAG_IV);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] data, byte[] key, byte[] signature) {
|
||||
return Arrays.equals(sign(data, key), signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the signature generated by data and key, with the original signed data. Uses
|
||||
* KTAG_IV as salt value.
|
||||
*/
|
||||
public boolean verify(byte[] data, byte[] signature) {
|
||||
return verify(data, KTAG_IV, signature);
|
||||
}
|
||||
}
|
|
@ -1,212 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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.util.encryption;
|
||||
|
||||
import static com.android.server.nearby.NearbyService.TAG;
|
||||
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* {@link android.nearby.BroadcastRequest#PRESENCE_VERSION_V1} for encryption and decryption.
|
||||
*/
|
||||
public class CryptorImpV1 extends Cryptor {
|
||||
|
||||
/**
|
||||
* In the form of "algorithm/mode/padding". Must be the same across broadcast and scan devices.
|
||||
*/
|
||||
private static final String CIPHER_ALGORITHM = "AES/CTR/NoPadding";
|
||||
|
||||
@VisibleForTesting
|
||||
static final String ENCRYPT_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
|
||||
|
||||
/** Length of encryption key required by AES/GCM encryption. */
|
||||
private static final int ENCRYPTION_KEY_SIZE = 32;
|
||||
|
||||
/** Length of salt required by AES/GCM encryption. */
|
||||
private static final int AES_CTR_IV_SIZE = 16;
|
||||
|
||||
/** Length HMAC tag */
|
||||
public static final int HMAC_TAG_SIZE = 16;
|
||||
|
||||
// 3 16 byte arrays known by both the encryptor and decryptor.
|
||||
private static final byte[] AK_IV =
|
||||
new byte[] {12, -59, 19, 23, 96, 57, -59, 19, 117, -31, -116, -61, 86, -25, -33, -78};
|
||||
private static final byte[] ASALT_IV =
|
||||
new byte[] {111, 48, -83, -79, -10, -102, -16, 73, 43, 55, 102, -127, 58, -19, -113, 4};
|
||||
private static final byte[] HK_IV =
|
||||
new byte[] {12, -59, 19, 23, 96, 57, -59, 19, 117, -31, -116, -61, 86, -25, -33, -78};
|
||||
|
||||
// Lazily instantiated when {@link #getInstance()} is called.
|
||||
@Nullable private static CryptorImpV1 sCryptor;
|
||||
|
||||
/** Returns an instance of CryptorImpV1. */
|
||||
public static CryptorImpV1 getInstance() {
|
||||
if (sCryptor == null) {
|
||||
sCryptor = new CryptorImpV1();
|
||||
}
|
||||
return sCryptor;
|
||||
}
|
||||
|
||||
private CryptorImpV1() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] encrypt(byte[] data, byte[] salt, byte[] authenticityKey) {
|
||||
if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
|
||||
Log.w(TAG, "Illegal authenticity key size");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generates a 32 bytes encryption key from authenticity_key
|
||||
byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, AK_IV, ENCRYPTION_KEY_SIZE);
|
||||
if (encryptionKey == null) {
|
||||
Log.e(TAG, "Failed to generate encryption key.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Encrypts the data using the encryption key
|
||||
SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
Log.e(TAG, "Failed to encrypt with secret key.", e);
|
||||
return null;
|
||||
}
|
||||
byte[] asalt = Cryptor.computeHkdf(salt, ASALT_IV, AES_CTR_IV_SIZE);
|
||||
if (asalt == null) {
|
||||
Log.e(TAG, "Failed to generate salt.");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(asalt));
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
Log.e(TAG, "Failed to initialize cipher.", e);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return cipher.doFinal(data);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||
Log.e(TAG, "Failed to encrypt with secret key.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] decrypt(byte[] encryptedData, byte[] salt, byte[] authenticityKey) {
|
||||
if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
|
||||
Log.w(TAG, "Illegal authenticity key size");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generates a 32 bytes encryption key from authenticity_key
|
||||
byte[] encryptionKey = Cryptor.computeHkdf(authenticityKey, AK_IV, ENCRYPTION_KEY_SIZE);
|
||||
if (encryptionKey == null) {
|
||||
Log.e(TAG, "Failed to generate encryption key.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Decrypts the data using the encryption key
|
||||
SecretKey secretKey = new SecretKeySpec(encryptionKey, ENCRYPT_ALGORITHM);
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
Log.e(TAG, "Failed to get cipher instance.", e);
|
||||
return null;
|
||||
}
|
||||
byte[] asalt = Cryptor.computeHkdf(salt, ASALT_IV, AES_CTR_IV_SIZE);
|
||||
if (asalt == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(asalt));
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
Log.e(TAG, "Failed to initialize cipher.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return cipher.doFinal(encryptedData);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||
Log.e(TAG, "Failed to decrypt bytes with secret key.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public byte[] sign(byte[] data, byte[] key) {
|
||||
return generateHmacTag(data, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSignatureLength() {
|
||||
return HMAC_TAG_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] data, byte[] key, byte[] signature) {
|
||||
return Arrays.equals(sign(data, key), signature);
|
||||
}
|
||||
|
||||
/** Generates a 16 bytes HMAC tag. This is used for decryptor to verify if the computed HMAC tag
|
||||
* is equal to HMAC tag in advertisement to see data integrity. */
|
||||
@Nullable
|
||||
@VisibleForTesting
|
||||
byte[] generateHmacTag(byte[] data, byte[] authenticityKey) {
|
||||
if (data == null || authenticityKey == null) {
|
||||
Log.e(TAG, "Not generate HMAC tag because of invalid data input.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (authenticityKey.length != AUTHENTICITY_KEY_BYTE_SIZE) {
|
||||
Log.e(TAG, "Illegal authenticity key size");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generates a 32 bytes HMAC key from authenticity_key
|
||||
byte[] hmacKey = Cryptor.computeHkdf(authenticityKey, HK_IV, AES_CTR_IV_SIZE);
|
||||
if (hmacKey == null) {
|
||||
Log.e(TAG, "Failed to generate HMAC key.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Generates a 16 bytes HMAC tag from authenticity_key
|
||||
return Cryptor.computeHkdf(data, hmacKey, HMAC_TAG_SIZE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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.util.encryption;
|
||||
|
||||
import static com.android.server.nearby.NearbyService.TAG;
|
||||
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.nearby.util.ArrayUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* MIC encryption and decryption for {@link android.nearby.BroadcastRequest#PRESENCE_VERSION_V1}
|
||||
* advertisement
|
||||
*/
|
||||
public class CryptorMicImp extends Cryptor {
|
||||
|
||||
public static final int MIC_LENGTH = 16;
|
||||
|
||||
private static final String ENCRYPT_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
|
||||
private static final byte[] AES_KEY_INFO_BYTES = "Unsigned Section AES key".getBytes(
|
||||
StandardCharsets.US_ASCII);
|
||||
private static final byte[] ADV_NONCE_INFO_BYTES_SALT_DE = "Unsigned Section IV".getBytes(
|
||||
StandardCharsets.US_ASCII);
|
||||
private static final byte[] ADV_NONCE_INFO_BYTES_ENCRYPTION_INFO_DE =
|
||||
"V1 derived salt".getBytes(StandardCharsets.US_ASCII);
|
||||
private static final byte[] METADATA_KEY_HMAC_KEY_INFO_BYTES =
|
||||
"Unsigned Section metadata key HMAC key".getBytes(StandardCharsets.US_ASCII);
|
||||
private static final byte[] MIC_HMAC_KEY_INFO_BYTES = "Unsigned Section HMAC key".getBytes(
|
||||
StandardCharsets.US_ASCII);
|
||||
private static final int AES_KEY_SIZE = 16;
|
||||
private static final int ADV_NONCE_SIZE_SALT_DE = 16;
|
||||
private static final int ADV_NONCE_SIZE_ENCRYPTION_INFO_DE = 12;
|
||||
private static final int HMAC_KEY_SIZE = 32;
|
||||
|
||||
// Lazily instantiated when {@link #getInstance()} is called.
|
||||
@Nullable
|
||||
private static CryptorMicImp sCryptor;
|
||||
|
||||
private CryptorMicImp() {
|
||||
}
|
||||
|
||||
/** Returns an instance of CryptorImpV1. */
|
||||
public static CryptorMicImp getInstance() {
|
||||
if (sCryptor == null) {
|
||||
sCryptor = new CryptorMicImp();
|
||||
}
|
||||
return sCryptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the meta data encryption key tag
|
||||
* @param metadataEncryptionKey used as identity
|
||||
* @param keySeed authenticity key saved in local and shared credential
|
||||
* @return bytes generated by hmac or {@code null} when there is an error
|
||||
*/
|
||||
@Nullable
|
||||
public static byte[] generateMetadataEncryptionKeyTag(byte[] metadataEncryptionKey,
|
||||
byte[] keySeed) {
|
||||
try {
|
||||
byte[] metadataKeyHmacKey = generateMetadataKeyHmacKey(keySeed);
|
||||
return Cryptor.generateHmac(/* algorithm= */ HMAC_SHA256_ALGORITHM, /* input= */
|
||||
metadataEncryptionKey, /* key= */ metadataKeyHmacKey);
|
||||
} catch (GeneralSecurityException e) {
|
||||
Log.e(TAG, "Failed to generate Metadata encryption key tag.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param salt from the 2 bytes Salt Data Element
|
||||
*/
|
||||
@Nullable
|
||||
public static byte[] generateAdvNonce(byte[] salt) throws GeneralSecurityException {
|
||||
return Cryptor.computeHkdf(
|
||||
/* macAlgorithm= */ HMAC_SHA256_ALGORITHM,
|
||||
/* ikm = */ salt,
|
||||
/* salt= */ NP_HKDF_SALT,
|
||||
/* info= */ ADV_NONCE_INFO_BYTES_SALT_DE,
|
||||
/* size= */ ADV_NONCE_SIZE_SALT_DE);
|
||||
}
|
||||
|
||||
/** Generates the 12 bytes nonce with salt from the 2 bytes Salt Data Element */
|
||||
@Nullable
|
||||
public static byte[] generateAdvNonce(byte[] salt, int deIndex)
|
||||
throws GeneralSecurityException {
|
||||
// go/nearby-specs-working-doc
|
||||
// Indices are encoded as big-endian unsigned 32-bit integers, starting at 1.
|
||||
// Index 0 is reserved
|
||||
byte[] indexBytes = new byte[4];
|
||||
indexBytes[3] = (byte) deIndex;
|
||||
byte[] info =
|
||||
ArrayUtils.concatByteArrays(ADV_NONCE_INFO_BYTES_ENCRYPTION_INFO_DE, indexBytes);
|
||||
return Cryptor.computeHkdf(
|
||||
/* macAlgorithm= */ HMAC_SHA256_ALGORITHM,
|
||||
/* ikm = */ salt,
|
||||
/* salt= */ NP_HKDF_SALT,
|
||||
/* info= */ info,
|
||||
/* size= */ ADV_NONCE_SIZE_ENCRYPTION_INFO_DE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] encrypt(byte[] input, byte[] iv, byte[] keySeed) {
|
||||
if (input == null || iv == null || keySeed == null) {
|
||||
return null;
|
||||
}
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
Log.e(TAG, "Failed to encrypt with secret key.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] aesKey;
|
||||
try {
|
||||
aesKey = generateAesKey(keySeed);
|
||||
} catch (GeneralSecurityException e) {
|
||||
Log.e(TAG, "Encryption failed because failed to generate the AES key.", e);
|
||||
return null;
|
||||
}
|
||||
if (aesKey == null) {
|
||||
Log.i(TAG, "Failed to generate the AES key.");
|
||||
return null;
|
||||
}
|
||||
SecretKey secretKey = new SecretKeySpec(aesKey, ENCRYPT_ALGORITHM);
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
Log.e(TAG, "Failed to initialize cipher.", e);
|
||||
return null;
|
||||
|
||||
}
|
||||
try {
|
||||
return cipher.doFinal(input);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||
Log.e(TAG, "Failed to encrypt with secret key.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public byte[] decrypt(byte[] encryptedData, byte[] iv, byte[] keySeed) {
|
||||
if (encryptedData == null || iv == null || keySeed == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Cipher cipher;
|
||||
try {
|
||||
cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
Log.e(TAG, "Failed to get cipher instance.", e);
|
||||
return null;
|
||||
}
|
||||
byte[] aesKey;
|
||||
try {
|
||||
aesKey = generateAesKey(keySeed);
|
||||
} catch (GeneralSecurityException e) {
|
||||
Log.e(TAG, "Decryption failed because failed to generate the AES key.", e);
|
||||
return null;
|
||||
}
|
||||
SecretKey secretKey = new SecretKeySpec(aesKey, ENCRYPT_ALGORITHM);
|
||||
try {
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
|
||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||
Log.e(TAG, "Failed to initialize cipher.", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return cipher.doFinal(encryptedData);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException e) {
|
||||
Log.e(TAG, "Failed to decrypt bytes with secret key.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public byte[] sign(byte[] data, byte[] key) {
|
||||
byte[] res = generateHmacTag(data, key);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSignatureLength() {
|
||||
return MIC_LENGTH;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(byte[] data, byte[] key, byte[] signature) {
|
||||
return Arrays.equals(sign(data, key), signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a 16 bytes HMAC tag. This is used for decryptor to verify if the computed HMAC tag
|
||||
* is equal to HMAC tag in advertisement to see data integrity.
|
||||
*
|
||||
* @param input concatenated advertisement UUID, header, section header, derived salt, and
|
||||
* section content
|
||||
* @param keySeed the MIC HMAC key is calculated using the derived key
|
||||
* @return the first 16 bytes of HMAC-SHA256 result
|
||||
*/
|
||||
@Nullable
|
||||
@VisibleForTesting
|
||||
byte[] generateHmacTag(byte[] input, byte[] keySeed) {
|
||||
try {
|
||||
if (input == null || keySeed == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] micHmacKey = generateMicHmacKey(keySeed);
|
||||
byte[] hmac = Cryptor.generateHmac(/* algorithm= */ HMAC_SHA256_ALGORITHM, /* input= */
|
||||
input, /* key= */ micHmacKey);
|
||||
if (ArrayUtils.isEmpty(hmac)) {
|
||||
return null;
|
||||
}
|
||||
return Arrays.copyOf(hmac, MIC_LENGTH);
|
||||
} catch (GeneralSecurityException e) {
|
||||
Log.e(TAG, "Failed to generate mic hmac key.", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] generateAesKey(byte[] keySeed) throws GeneralSecurityException {
|
||||
return Cryptor.computeHkdf(
|
||||
/* macAlgorithm= */ HMAC_SHA256_ALGORITHM,
|
||||
/* ikm = */ keySeed,
|
||||
/* salt= */ NP_HKDF_SALT,
|
||||
/* info= */ AES_KEY_INFO_BYTES,
|
||||
/* size= */ AES_KEY_SIZE);
|
||||
}
|
||||
|
||||
private static byte[] generateMetadataKeyHmacKey(byte[] keySeed)
|
||||
throws GeneralSecurityException {
|
||||
return generateHmacKey(keySeed, METADATA_KEY_HMAC_KEY_INFO_BYTES);
|
||||
}
|
||||
|
||||
private static byte[] generateMicHmacKey(byte[] keySeed) throws GeneralSecurityException {
|
||||
return generateHmacKey(keySeed, MIC_HMAC_KEY_INFO_BYTES);
|
||||
}
|
||||
|
||||
private static byte[] generateHmacKey(byte[] keySeed, byte[] info)
|
||||
throws GeneralSecurityException {
|
||||
return Cryptor.computeHkdf(
|
||||
/* macAlgorithm= */ HMAC_SHA256_ALGORITHM,
|
||||
/* ikm = */ keySeed,
|
||||
/* salt= */ NP_HKDF_SALT,
|
||||
/* info= */ info,
|
||||
/* size= */ HMAC_KEY_SIZE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (C) 2023 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.presence;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import com.android.server.nearby.presence.EncryptionInfo.EncodingScheme;
|
||||
import com.android.server.nearby.util.ArrayUtils;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
/**
|
||||
* Unit test for {@link EncryptionInfo}.
|
||||
*/
|
||||
public class EncryptionInfoTest {
|
||||
private static final byte[] SALT =
|
||||
new byte[]{25, -21, 35, -108, -26, -126, 99, 60, 110, 45, -116, 34, 91, 126, -23, 127};
|
||||
|
||||
@Test
|
||||
public void test_illegalLength() {
|
||||
byte[] data = new byte[]{1, 2};
|
||||
assertThrows(IllegalArgumentException.class, () -> new EncryptionInfo(data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_illegalEncodingScheme() {
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new EncryptionInfo(ArrayUtils.append((byte) 0b10110000, SALT)));
|
||||
assertThrows(IllegalArgumentException.class,
|
||||
() -> new EncryptionInfo(ArrayUtils.append((byte) 0b01101000, SALT)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getMethods_signature() {
|
||||
byte[] data = ArrayUtils.append((byte) 0b10001000, SALT);
|
||||
EncryptionInfo info = new EncryptionInfo(data);
|
||||
assertThat(info.getEncodingScheme()).isEqualTo(EncodingScheme.SIGNATURE);
|
||||
assertThat(info.getSalt()).isEqualTo(SALT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_getMethods_mic() {
|
||||
byte[] data = ArrayUtils.append((byte) 0b10000000, SALT);
|
||||
EncryptionInfo info = new EncryptionInfo(data);
|
||||
assertThat(info.getEncodingScheme()).isEqualTo(EncodingScheme.MIC);
|
||||
assertThat(info.getSalt()).isEqualTo(SALT);
|
||||
}
|
||||
@Test
|
||||
public void test_toBytes() {
|
||||
byte[] data = EncryptionInfo.toByte(EncodingScheme.MIC, SALT);
|
||||
EncryptionInfo info = new EncryptionInfo(data);
|
||||
assertThat(info.getEncodingScheme()).isEqualTo(EncodingScheme.MIC);
|
||||
assertThat(info.getSalt()).isEqualTo(SALT);
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package com.android.server.nearby.presence;
|
||||
|
||||
import static com.android.server.nearby.presence.PresenceConstants.PRESENCE_UUID_BYTES;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.nearby.BroadcastRequest;
|
||||
|
@ -25,19 +27,22 @@ import android.nearby.PresenceCredential;
|
|||
import android.nearby.PrivateCredential;
|
||||
import android.nearby.PublicCredential;
|
||||
|
||||
import com.android.server.nearby.util.encryption.CryptorImpIdentityV1;
|
||||
import com.android.server.nearby.util.encryption.CryptorImpV1;
|
||||
import com.android.server.nearby.util.ArrayUtils;
|
||||
import com.android.server.nearby.util.encryption.CryptorMicImp;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ExtendedAdvertisementTest {
|
||||
private static final int EXTENDED_ADVERTISEMENT_BYTE_LENGTH = 67;
|
||||
private static final int IDENTITY_TYPE = PresenceCredential.IDENTITY_TYPE_PRIVATE;
|
||||
private static final int DATA_TYPE_ACTION = 6;
|
||||
private static final int DATA_TYPE_MODEL_ID = 7;
|
||||
private static final int DATA_TYPE_BLE_ADDRESS = 101;
|
||||
private static final int DATA_TYPE_PUBLIC_IDENTITY = 3;
|
||||
|
@ -49,18 +54,23 @@ public class ExtendedAdvertisementTest {
|
|||
private static final DataElement BLE_ADDRESS_ELEMENT =
|
||||
new DataElement(DATA_TYPE_BLE_ADDRESS, BLE_ADDRESS);
|
||||
|
||||
private static final byte[] IDENTITY =
|
||||
new byte[]{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4};
|
||||
private static final byte[] METADATA_ENCRYPTION_KEY =
|
||||
new byte[]{-39, -55, 115, 78, -57, 40, 115, 0, -112, 86, -86, 7, -42, 68, 11, 12};
|
||||
private static final int MEDIUM_TYPE_BLE = 0;
|
||||
private static final byte[] SALT = {2, 3};
|
||||
|
||||
private static final int PRESENCE_ACTION_1 = 1;
|
||||
private static final int PRESENCE_ACTION_2 = 2;
|
||||
private static final DataElement PRESENCE_ACTION_DE_1 =
|
||||
new DataElement(DATA_TYPE_ACTION, new byte[]{PRESENCE_ACTION_1});
|
||||
private static final DataElement PRESENCE_ACTION_DE_2 =
|
||||
new DataElement(DATA_TYPE_ACTION, new byte[]{PRESENCE_ACTION_2});
|
||||
|
||||
private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
|
||||
private static final byte[] AUTHENTICITY_KEY =
|
||||
new byte[]{-97, 10, 107, -86, 25, 65, -54, -95, -72, 59, 54, 93, 9, 3, -24, -88};
|
||||
private static final byte[] PUBLIC_KEY =
|
||||
new byte[] {
|
||||
new byte[]{
|
||||
48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72, -50, 61,
|
||||
66, 0, 4, -56, -39, -92, 69, 0, 52, 23, 67, 83, -14, 75, 52, -14, -5, -41, 48,
|
||||
-83, 31, 42, -39, 102, -13, 22, -73, -73, 86, 30, -96, -84, -13, 4, 122, 104,
|
||||
|
@ -68,7 +78,7 @@ public class ExtendedAdvertisementTest {
|
|||
123, 41, -119, -25, 1, -112, 112
|
||||
};
|
||||
private static final byte[] ENCRYPTED_METADATA_BYTES =
|
||||
new byte[] {
|
||||
new byte[]{
|
||||
-44, -25, -95, -124, -7, 90, 116, -8, 7, -120, -23, -22, -106, -44, -19, 61,
|
||||
-18, 39, 29, 78, 108, -11, -39, 85, -30, 64, -99, 102, 65, 37, -42, 114, -37,
|
||||
88, -112, 8, -75, -53, 23, -16, -104, 67, 49, 48, -53, 73, -109, 44, -23, -11,
|
||||
|
@ -78,23 +88,65 @@ public class ExtendedAdvertisementTest {
|
|||
-4, -46, -30, -85, -50, 100, 46, -66, -128, 7, 66, 9, 88, 95, 12, -13, 81, -91,
|
||||
};
|
||||
private static final byte[] METADATA_ENCRYPTION_KEY_TAG =
|
||||
new byte[] {-126, -104, 1, -1, 26, -46, -68, -86};
|
||||
new byte[]{-100, 102, -35, -99, 66, -85, -55, -58, -52, 11, -74, 102, 109, -89, 1, -34,
|
||||
45, 43, 107, -60, 99, -21, 28, 34, 31, -100, -96, 108, 108, -18, 107, 5};
|
||||
|
||||
private static final String ENCODED_ADVERTISEMENT_ENCRYPTION_INFO =
|
||||
"2091911000DE2A89ED98474AF3E41E48487E8AEBDE90014C18BCB9F9AAC5C11A1BE00A10A5DCD2C49A74BE"
|
||||
+ "BAF0FE72FD5053B9DF8B9976C80BE0DCE8FEE83F1BFA9A89EB176CA48EE4ED5D15C6CDAD6B9E"
|
||||
+ "41187AA6316D7BFD8E454A53971AC00836F7AB0771FF0534050037D49C6AEB18CF9F8590E5CD"
|
||||
+ "EE2FBC330FCDC640C63F0735B7E3F02FE61A0496EF976A158AD3455D";
|
||||
private static final byte[] METADATA_ENCRYPTION_KEY_TAG_2 =
|
||||
new byte[]{-54, -39, 41, 16, 61, 79, -116, 14, 94, 0, 84, 45, 26, -108, 66, -48, 124,
|
||||
-81, 61, 56, -98, -47, 14, -19, 116, 106, -27, 123, -81, 49, 83, -42};
|
||||
|
||||
private static final String DEVICE_NAME = "test_device";
|
||||
|
||||
private static final byte[] SALT_16 =
|
||||
ArrayUtils.stringToBytes("DE2A89ED98474AF3E41E48487E8AEBDE");
|
||||
private static final byte[] AUTHENTICITY_KEY_2 = ArrayUtils.stringToBytes(
|
||||
"959D2F3CAB8EE4A2DEB0255C03762CF5D39EB919300420E75A089050FB025E20");
|
||||
private static final byte[] METADATA_ENCRYPTION_KEY_2 = ArrayUtils.stringToBytes(
|
||||
"EF5E9A0867560E52AE1F05FCA7E48D29");
|
||||
|
||||
private static final DataElement DE1 = new DataElement(571, ArrayUtils.stringToBytes(
|
||||
"537F96FD94E13BE589F0141145CFC0EEC4F86FBDB2"));
|
||||
private static final DataElement DE2 = new DataElement(541, ArrayUtils.stringToBytes(
|
||||
"D301FFB24B5B"));
|
||||
private static final DataElement DE3 = new DataElement(51, ArrayUtils.stringToBytes(
|
||||
"EA95F07C25B75C04E1B2B8731F6A55BA379FB141"));
|
||||
private static final DataElement DE4 = new DataElement(729, ArrayUtils.stringToBytes(
|
||||
"2EFD3101E2311BBB108F0A7503907EAF0C2EAAA60CDA8D33A294C4CEACE0"));
|
||||
private static final DataElement DE5 = new DataElement(411, ArrayUtils.stringToBytes("B0"));
|
||||
|
||||
private PresenceBroadcastRequest.Builder mBuilder;
|
||||
private PresenceBroadcastRequest.Builder mBuilderCredentialInfo;
|
||||
private PrivateCredential mPrivateCredential;
|
||||
private PrivateCredential mPrivateCredential2;
|
||||
|
||||
private PublicCredential mPublicCredential;
|
||||
private PublicCredential mPublicCredential2;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
mPrivateCredential =
|
||||
new PrivateCredential.Builder(SECRET_ID, AUTHENTICITY_KEY, IDENTITY, DEVICE_NAME)
|
||||
new PrivateCredential.Builder(
|
||||
SECRET_ID, AUTHENTICITY_KEY, METADATA_ENCRYPTION_KEY, DEVICE_NAME)
|
||||
.setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
|
||||
.build();
|
||||
mPrivateCredential2 =
|
||||
new PrivateCredential.Builder(
|
||||
SECRET_ID, AUTHENTICITY_KEY_2, METADATA_ENCRYPTION_KEY_2, DEVICE_NAME)
|
||||
.setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
|
||||
.build();
|
||||
mPublicCredential =
|
||||
new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY, PUBLIC_KEY,
|
||||
ENCRYPTED_METADATA_BYTES, METADATA_ENCRYPTION_KEY_TAG)
|
||||
.build();
|
||||
mPublicCredential2 =
|
||||
new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY_2, PUBLIC_KEY,
|
||||
ENCRYPTED_METADATA_BYTES, METADATA_ENCRYPTION_KEY_TAG_2)
|
||||
.build();
|
||||
mBuilder =
|
||||
new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
|
||||
SALT, mPrivateCredential)
|
||||
|
@ -103,6 +155,16 @@ public class ExtendedAdvertisementTest {
|
|||
.addAction(PRESENCE_ACTION_2)
|
||||
.addExtendedProperty(new DataElement(DATA_TYPE_BLE_ADDRESS, BLE_ADDRESS))
|
||||
.addExtendedProperty(new DataElement(DATA_TYPE_MODEL_ID, MODE_ID_DATA));
|
||||
|
||||
mBuilderCredentialInfo =
|
||||
new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
|
||||
SALT_16, mPrivateCredential2)
|
||||
.setVersion(BroadcastRequest.PRESENCE_VERSION_V1)
|
||||
.addExtendedProperty(DE1)
|
||||
.addExtendedProperty(DE2)
|
||||
.addExtendedProperty(DE3)
|
||||
.addExtendedProperty(DE4)
|
||||
.addExtendedProperty(DE5);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -112,36 +174,50 @@ public class ExtendedAdvertisementTest {
|
|||
|
||||
assertThat(originalAdvertisement.getActions())
|
||||
.containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2);
|
||||
assertThat(originalAdvertisement.getIdentity()).isEqualTo(IDENTITY);
|
||||
assertThat(originalAdvertisement.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY);
|
||||
assertThat(originalAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE);
|
||||
assertThat(originalAdvertisement.getLength()).isEqualTo(66);
|
||||
assertThat(originalAdvertisement.getVersion()).isEqualTo(
|
||||
BroadcastRequest.PRESENCE_VERSION_V1);
|
||||
assertThat(originalAdvertisement.getSalt()).isEqualTo(SALT);
|
||||
assertThat(originalAdvertisement.getDataElements())
|
||||
.containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT);
|
||||
.containsExactly(PRESENCE_ACTION_DE_1, PRESENCE_ACTION_DE_2,
|
||||
MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT);
|
||||
assertThat(originalAdvertisement.getLength()).isEqualTo(EXTENDED_ADVERTISEMENT_BYTE_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_createFromRequest_credentialInfo() {
|
||||
ExtendedAdvertisement originalAdvertisement = ExtendedAdvertisement.createFromRequest(
|
||||
mBuilderCredentialInfo.build());
|
||||
|
||||
assertThat(originalAdvertisement.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY_2);
|
||||
assertThat(originalAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE);
|
||||
assertThat(originalAdvertisement.getVersion()).isEqualTo(
|
||||
BroadcastRequest.PRESENCE_VERSION_V1);
|
||||
assertThat(originalAdvertisement.getSalt()).isEqualTo(SALT_16);
|
||||
assertThat(originalAdvertisement.getDataElements())
|
||||
.containsExactly(DE1, DE2, DE3, DE4, DE5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_createFromRequest_encodeAndDecode() {
|
||||
ExtendedAdvertisement originalAdvertisement = ExtendedAdvertisement.createFromRequest(
|
||||
mBuilder.build());
|
||||
|
||||
byte[] generatedBytes = originalAdvertisement.toBytes();
|
||||
|
||||
ExtendedAdvertisement newAdvertisement =
|
||||
ExtendedAdvertisement.fromBytes(generatedBytes, mPublicCredential);
|
||||
|
||||
assertThat(newAdvertisement.getActions())
|
||||
.containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2);
|
||||
assertThat(newAdvertisement.getIdentity()).isEqualTo(IDENTITY);
|
||||
assertThat(newAdvertisement.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY);
|
||||
assertThat(newAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE);
|
||||
assertThat(newAdvertisement.getLength()).isEqualTo(66);
|
||||
assertThat(newAdvertisement.getLength()).isEqualTo(EXTENDED_ADVERTISEMENT_BYTE_LENGTH);
|
||||
assertThat(newAdvertisement.getVersion()).isEqualTo(
|
||||
BroadcastRequest.PRESENCE_VERSION_V1);
|
||||
assertThat(newAdvertisement.getSalt()).isEqualTo(SALT);
|
||||
assertThat(newAdvertisement.getDataElements())
|
||||
.containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT);
|
||||
.containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT,
|
||||
PRESENCE_ACTION_DE_1, PRESENCE_ACTION_DE_2);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -170,45 +246,77 @@ public class ExtendedAdvertisementTest {
|
|||
.setVersion(BroadcastRequest.PRESENCE_VERSION_V1)
|
||||
.addAction(PRESENCE_ACTION_1);
|
||||
assertThat(ExtendedAdvertisement.createFromRequest(builder2.build())).isNull();
|
||||
|
||||
// empty action
|
||||
PresenceBroadcastRequest.Builder builder3 =
|
||||
new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
|
||||
SALT, mPrivateCredential)
|
||||
.setVersion(BroadcastRequest.PRESENCE_VERSION_V1);
|
||||
assertThat(ExtendedAdvertisement.createFromRequest(builder3.build())).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_toBytes() {
|
||||
public void test_toBytesSalt() throws Exception {
|
||||
ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build());
|
||||
assertThat(adv.toBytes()).isEqualTo(getExtendedAdvertisementByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_fromBytes() {
|
||||
public void test_fromBytesSalt() throws Exception {
|
||||
byte[] originalBytes = getExtendedAdvertisementByteArray();
|
||||
ExtendedAdvertisement adv =
|
||||
ExtendedAdvertisement.fromBytes(originalBytes, mPublicCredential);
|
||||
|
||||
assertThat(adv.getActions())
|
||||
.containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2);
|
||||
assertThat(adv.getIdentity()).isEqualTo(IDENTITY);
|
||||
assertThat(adv.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY);
|
||||
assertThat(adv.getIdentityType()).isEqualTo(IDENTITY_TYPE);
|
||||
assertThat(adv.getLength()).isEqualTo(66);
|
||||
assertThat(adv.getLength()).isEqualTo(EXTENDED_ADVERTISEMENT_BYTE_LENGTH);
|
||||
assertThat(adv.getVersion()).isEqualTo(
|
||||
BroadcastRequest.PRESENCE_VERSION_V1);
|
||||
assertThat(adv.getSalt()).isEqualTo(SALT);
|
||||
assertThat(adv.getDataElements())
|
||||
.containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT);
|
||||
.containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT,
|
||||
PRESENCE_ACTION_DE_1, PRESENCE_ACTION_DE_2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_toBytesCredentialElement() {
|
||||
ExtendedAdvertisement adv =
|
||||
ExtendedAdvertisement.createFromRequest(mBuilderCredentialInfo.build());
|
||||
assertThat(ArrayUtils.bytesToStringUppercase(adv.toBytes())).isEqualTo(
|
||||
ENCODED_ADVERTISEMENT_ENCRYPTION_INFO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_fromBytesCredentialElement() {
|
||||
ExtendedAdvertisement adv =
|
||||
ExtendedAdvertisement.fromBytes(
|
||||
ArrayUtils.stringToBytes(ENCODED_ADVERTISEMENT_ENCRYPTION_INFO),
|
||||
mPublicCredential2);
|
||||
assertThat(adv.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY_2);
|
||||
assertThat(adv.getIdentityType()).isEqualTo(IDENTITY_TYPE);
|
||||
assertThat(adv.getVersion()).isEqualTo(BroadcastRequest.PRESENCE_VERSION_V1);
|
||||
assertThat(adv.getSalt()).isEqualTo(SALT_16);
|
||||
assertThat(adv.getDataElements()).containsExactly(DE1, DE2, DE3, DE4, DE5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_fromBytes_metadataTagNotMatched_fail() throws Exception {
|
||||
byte[] originalBytes = getExtendedAdvertisementByteArray();
|
||||
PublicCredential credential =
|
||||
new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY, PUBLIC_KEY,
|
||||
ENCRYPTED_METADATA_BYTES,
|
||||
new byte[]{113, 90, -55, 73, 25, -9, 55, -44, 102, 44, 81, -68, 101, 21, 32,
|
||||
92, -107, 3, 108, 90, 28, -73, 16, 49, -95, -121, 8, -45, -27, 16,
|
||||
6, 108})
|
||||
.build();
|
||||
ExtendedAdvertisement adv =
|
||||
ExtendedAdvertisement.fromBytes(originalBytes, credential);
|
||||
assertThat(adv).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_toString() {
|
||||
ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build());
|
||||
assertThat(adv.toString()).isEqualTo("ExtendedAdvertisement:"
|
||||
+ "<VERSION: 1, length: 66, dataElementCount: 2, identityType: 1, "
|
||||
+ "identity: [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4], salt: [2, 3],"
|
||||
+ "<VERSION: 1, length: " + EXTENDED_ADVERTISEMENT_BYTE_LENGTH
|
||||
+ ", dataElementCount: 4, identityType: 1, "
|
||||
+ "identity: " + Arrays.toString(METADATA_ENCRYPTION_KEY)
|
||||
+ ", salt: [2, 3],"
|
||||
+ " actions: [1, 2]>");
|
||||
}
|
||||
|
||||
|
@ -222,26 +330,22 @@ public class ExtendedAdvertisementTest {
|
|||
assertThat(adv.getDataElements(DATA_TYPE_PUBLIC_IDENTITY)).isEmpty();
|
||||
}
|
||||
|
||||
private static byte[] getExtendedAdvertisementByteArray() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(66);
|
||||
private static byte[] getExtendedAdvertisementByteArray() throws Exception {
|
||||
CryptorMicImp cryptor = CryptorMicImp.getInstance();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(EXTENDED_ADVERTISEMENT_BYTE_LENGTH);
|
||||
buffer.put((byte) 0b00100000); // Header V1
|
||||
buffer.put((byte) 0b00100000); // Salt header: length 2, type 0
|
||||
buffer.put(
|
||||
(byte) (EXTENDED_ADVERTISEMENT_BYTE_LENGTH - 2)); // Section header (section length)
|
||||
|
||||
// Salt data
|
||||
buffer.put(SALT);
|
||||
// Salt header: length 2, type 0
|
||||
byte[] saltBytes = ArrayUtils.concatByteArrays(new byte[]{(byte) 0b00100000}, SALT);
|
||||
buffer.put(saltBytes);
|
||||
// Identity header: length 16, type 1 (private identity)
|
||||
buffer.put(new byte[]{(byte) 0b10010000, (byte) 0b00000001});
|
||||
// Identity data
|
||||
buffer.put(CryptorImpIdentityV1.getInstance().encrypt(IDENTITY, SALT, AUTHENTICITY_KEY));
|
||||
byte[] identityHeader = new byte[]{(byte) 0b10010000, (byte) 0b00000001};
|
||||
buffer.put(identityHeader);
|
||||
|
||||
ByteBuffer deBuffer = ByteBuffer.allocate(28);
|
||||
// Action1 header: length 1, type 6
|
||||
deBuffer.put(new byte[]{(byte) 0b00010110});
|
||||
// Action1 data
|
||||
deBuffer.put((byte) PRESENCE_ACTION_1);
|
||||
// Action2 header: length 1, type 6
|
||||
deBuffer.put(new byte[]{(byte) 0b00010110});
|
||||
// Action2 data
|
||||
deBuffer.put((byte) PRESENCE_ACTION_2);
|
||||
// Ble address header: length 7, type 102
|
||||
deBuffer.put(new byte[]{(byte) 0b10000111, (byte) 0b01100101});
|
||||
// Ble address data
|
||||
|
@ -250,11 +354,30 @@ public class ExtendedAdvertisementTest {
|
|||
deBuffer.put(new byte[]{(byte) 0b10001101, (byte) 0b00000111});
|
||||
// model id data
|
||||
deBuffer.put(MODE_ID_DATA);
|
||||
// Action1 header: length 1, type 6
|
||||
deBuffer.put(new byte[]{(byte) 0b00010110});
|
||||
// Action1 data
|
||||
deBuffer.put((byte) PRESENCE_ACTION_1);
|
||||
// Action2 header: length 1, type 6
|
||||
deBuffer.put(new byte[]{(byte) 0b00010110});
|
||||
// Action2 data
|
||||
deBuffer.put((byte) PRESENCE_ACTION_2);
|
||||
byte[] deBytes = deBuffer.array();
|
||||
byte[] nonce = CryptorMicImp.generateAdvNonce(SALT);
|
||||
byte[] ciphertext =
|
||||
cryptor.encrypt(
|
||||
ArrayUtils.concatByteArrays(METADATA_ENCRYPTION_KEY, deBytes),
|
||||
nonce, AUTHENTICITY_KEY);
|
||||
buffer.put(ciphertext);
|
||||
|
||||
byte[] data = deBuffer.array();
|
||||
CryptorImpV1 cryptor = CryptorImpV1.getInstance();
|
||||
buffer.put(cryptor.encrypt(data, SALT, AUTHENTICITY_KEY));
|
||||
buffer.put(cryptor.sign(data, AUTHENTICITY_KEY));
|
||||
byte[] dataToSign = ArrayUtils.concatByteArrays(
|
||||
PRESENCE_UUID_BYTES, /* UUID */
|
||||
new byte[]{(byte) 0b00100000}, /* header */
|
||||
new byte[]{(byte) (EXTENDED_ADVERTISEMENT_BYTE_LENGTH - 2)} /* sectionHeader */,
|
||||
saltBytes, /* salt */
|
||||
nonce, identityHeader, ciphertext);
|
||||
byte[] mic = cryptor.sign(dataToSign, AUTHENTICITY_KEY);
|
||||
buffer.put(mic);
|
||||
|
||||
return buffer.array();
|
||||
}
|
||||
|
|
|
@ -18,8 +18,6 @@ package com.android.server.nearby.util;
|
|||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.test.filters.SdkSuppress;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public final class ArrayUtilsTest {
|
||||
|
@ -30,51 +28,58 @@ public final class ArrayUtilsTest {
|
|||
private static final byte[] BYTES_ALL = new byte[] {7, 9, 8};
|
||||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = 32, codeName = "T")
|
||||
public void testConcatByteArraysNoInput() {
|
||||
assertThat(ArrayUtils.concatByteArrays().length).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = 32, codeName = "T")
|
||||
public void testConcatByteArraysOneEmptyArray() {
|
||||
assertThat(ArrayUtils.concatByteArrays(BYTES_EMPTY).length).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = 32, codeName = "T")
|
||||
public void testConcatByteArraysOneNonEmptyArray() {
|
||||
assertThat(ArrayUtils.concatByteArrays(BYTES_ONE)).isEqualTo(BYTES_ONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = 32, codeName = "T")
|
||||
public void testConcatByteArraysMultipleNonEmptyArrays() {
|
||||
assertThat(ArrayUtils.concatByteArrays(BYTES_ONE, BYTES_TWO)).isEqualTo(BYTES_ALL);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = 32, codeName = "T")
|
||||
public void testConcatByteArraysMultipleArrays() {
|
||||
assertThat(ArrayUtils.concatByteArrays(BYTES_ONE, BYTES_EMPTY, BYTES_TWO))
|
||||
.isEqualTo(BYTES_ALL);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = 32, codeName = "T")
|
||||
public void testIsEmptyNull_returnsTrue() {
|
||||
assertThat(ArrayUtils.isEmpty(null)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = 32, codeName = "T")
|
||||
public void testIsEmpty_returnsTrue() {
|
||||
assertThat(ArrayUtils.isEmpty(new byte[]{})).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SdkSuppress(minSdkVersion = 32, codeName = "T")
|
||||
public void testIsEmpty_returnsFalse() {
|
||||
assertThat(ArrayUtils.isEmpty(BYTES_ALL)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppendByte() {
|
||||
assertThat(ArrayUtils.append((byte) 2, BYTES_ONE)).isEqualTo(new byte[]{2, 7, 9});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppendByteNull() {
|
||||
assertThat(ArrayUtils.append((byte) 2, null)).isEqualTo(new byte[]{2});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAppendByteToArray() {
|
||||
assertThat(ArrayUtils.append(BYTES_ONE, (byte) 2)).isEqualTo(new byte[]{7, 9, 2});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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.util.encryption;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class CryptorImpIdentityV1Test {
|
||||
private static final String TAG = "CryptorImpIdentityV1Test";
|
||||
private static final byte[] SALT = new byte[] {102, 22};
|
||||
private static final byte[] DATA =
|
||||
new byte[] {107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122};
|
||||
private static final byte[] AUTHENTICITY_KEY =
|
||||
new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100};
|
||||
|
||||
@Test
|
||||
public void test_encrypt_decrypt() {
|
||||
Cryptor identityCryptor = CryptorImpIdentityV1.getInstance();
|
||||
byte[] encryptedData = identityCryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY);
|
||||
|
||||
assertThat(identityCryptor.decrypt(encryptedData, SALT, AUTHENTICITY_KEY)).isEqualTo(DATA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_encryption() {
|
||||
Cryptor identityCryptor = CryptorImpIdentityV1.getInstance();
|
||||
byte[] encryptedData = identityCryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY);
|
||||
|
||||
// for debugging
|
||||
Log.d(TAG, "encrypted data is: " + Arrays.toString(encryptedData));
|
||||
|
||||
assertThat(encryptedData).isEqualTo(getEncryptedData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_decryption() {
|
||||
Cryptor identityCryptor = CryptorImpIdentityV1.getInstance();
|
||||
byte[] decryptedData =
|
||||
identityCryptor.decrypt(getEncryptedData(), SALT, AUTHENTICITY_KEY);
|
||||
// for debugging
|
||||
Log.d(TAG, "decrypted data is: " + Arrays.toString(decryptedData));
|
||||
|
||||
assertThat(decryptedData).isEqualTo(DATA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateHmacTag() {
|
||||
CryptorImpIdentityV1 identityCryptor = CryptorImpIdentityV1.getInstance();
|
||||
byte[] generatedTag = identityCryptor.sign(DATA);
|
||||
byte[] expectedTag = new byte[]{50, 116, 95, -87, 63, 123, -79, -43};
|
||||
assertThat(generatedTag).isEqualTo(expectedTag);
|
||||
}
|
||||
|
||||
private static byte[] getEncryptedData() {
|
||||
return new byte[]{6, -31, -32, -123, 43, -92, -47, -110, -65, 126, -15, -51, -19, -43};
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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.util.encryption;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Unit test for {@link CryptorImpV1}
|
||||
*/
|
||||
public final class CryptorImpV1Test {
|
||||
private static final String TAG = "CryptorImpV1Test";
|
||||
private static final byte[] SALT = new byte[] {102, 22};
|
||||
private static final byte[] DATA =
|
||||
new byte[] {107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122};
|
||||
private static final byte[] AUTHENTICITY_KEY =
|
||||
new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100};
|
||||
|
||||
@Test
|
||||
public void test_encryption() {
|
||||
Cryptor v1Cryptor = CryptorImpV1.getInstance();
|
||||
byte[] encryptedData = v1Cryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY);
|
||||
|
||||
// for debugging
|
||||
Log.d(TAG, "encrypted data is: " + Arrays.toString(encryptedData));
|
||||
|
||||
assertThat(encryptedData).isEqualTo(getEncryptedData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_encryption_invalidInput() {
|
||||
Cryptor v1Cryptor = CryptorImpV1.getInstance();
|
||||
assertThat(v1Cryptor.encrypt(DATA, SALT, new byte[]{1, 2, 3, 4, 6})).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_decryption() {
|
||||
Cryptor v1Cryptor = CryptorImpV1.getInstance();
|
||||
byte[] decryptedData =
|
||||
v1Cryptor.decrypt(getEncryptedData(), SALT, AUTHENTICITY_KEY);
|
||||
// for debugging
|
||||
Log.d(TAG, "decrypted data is: " + Arrays.toString(decryptedData));
|
||||
|
||||
assertThat(decryptedData).isEqualTo(DATA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_decryption_invalidInput() {
|
||||
Cryptor v1Cryptor = CryptorImpV1.getInstance();
|
||||
assertThat(v1Cryptor.decrypt(getEncryptedData(), SALT, new byte[]{1, 2, 3, 4, 6})).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateSign() {
|
||||
CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
|
||||
byte[] generatedTag = v1Cryptor.sign(DATA, AUTHENTICITY_KEY);
|
||||
byte[] expectedTag = new byte[]{
|
||||
100, 88, -104, 80, -66, 107, -38, 95, 34, 40, -56, -23, -90, 90, -87, 12};
|
||||
assertThat(generatedTag).isEqualTo(expectedTag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_verify() {
|
||||
CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
|
||||
byte[] expectedTag = new byte[]{
|
||||
100, 88, -104, 80, -66, 107, -38, 95, 34, 40, -56, -23, -90, 90, -87, 12};
|
||||
|
||||
assertThat(v1Cryptor.verify(DATA, AUTHENTICITY_KEY, expectedTag)).isTrue();
|
||||
assertThat(v1Cryptor.verify(DATA, AUTHENTICITY_KEY, DATA)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_generateHmacTag_sameResult() {
|
||||
CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
|
||||
byte[] res1 = v1Cryptor.generateHmacTag(DATA, AUTHENTICITY_KEY);
|
||||
assertThat(res1)
|
||||
.isEqualTo(v1Cryptor.generateHmacTag(DATA, AUTHENTICITY_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_generateHmacTag_nullData() {
|
||||
CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
|
||||
assertThat(v1Cryptor.generateHmacTag(/* data= */ null, AUTHENTICITY_KEY)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_generateHmacTag_nullKey() {
|
||||
CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance();
|
||||
assertThat(v1Cryptor.generateHmacTag(DATA, /* authenticityKey= */ null)).isNull();
|
||||
}
|
||||
|
||||
private static byte[] getEncryptedData() {
|
||||
return new byte[]{-92, 94, -99, -97, 81, -48, -7, 119, -64, -22, 45, -49, -50, 92};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (C) 2022 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.util.encryption;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
/**
|
||||
* Unit test for {@link CryptorMicImp}
|
||||
*/
|
||||
public final class CryptorMicImpTest {
|
||||
private static final String TAG = "CryptorImpV1Test";
|
||||
private static final byte[] SALT = new byte[]{102, 22};
|
||||
private static final byte[] DATA =
|
||||
new byte[]{107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122};
|
||||
private static final byte[] AUTHENTICITY_KEY =
|
||||
new byte[]{-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100};
|
||||
|
||||
private static byte[] getEncryptedData() {
|
||||
return new byte[]{112, 23, -111, 87, 122, -27, 45, -25, -35, 84, -89, 115, 61, 113};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_encryption() throws Exception {
|
||||
Cryptor v1Cryptor = CryptorMicImp.getInstance();
|
||||
byte[] encryptedData =
|
||||
v1Cryptor.encrypt(DATA, CryptorMicImp.generateAdvNonce(SALT), AUTHENTICITY_KEY);
|
||||
assertThat(encryptedData).isEqualTo(getEncryptedData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_decryption() throws Exception {
|
||||
Cryptor v1Cryptor = CryptorMicImp.getInstance();
|
||||
byte[] decryptedData =
|
||||
v1Cryptor.decrypt(getEncryptedData(), CryptorMicImp.generateAdvNonce(SALT),
|
||||
AUTHENTICITY_KEY);
|
||||
assertThat(decryptedData).isEqualTo(DATA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_verify() {
|
||||
CryptorMicImp v1Cryptor = CryptorMicImp.getInstance();
|
||||
byte[] expectedTag = new byte[]{
|
||||
-80, -51, -101, -7, -65, 110, 37, 68, 122, -128, 57, -90, -115, -59, -61, 46};
|
||||
assertThat(v1Cryptor.verify(DATA, AUTHENTICITY_KEY, expectedTag)).isTrue();
|
||||
assertThat(v1Cryptor.verify(DATA, AUTHENTICITY_KEY, DATA)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_generateHmacTag_sameResult() {
|
||||
CryptorMicImp v1Cryptor = CryptorMicImp.getInstance();
|
||||
byte[] res1 = v1Cryptor.generateHmacTag(DATA, AUTHENTICITY_KEY);
|
||||
assertThat(res1)
|
||||
.isEqualTo(v1Cryptor.generateHmacTag(DATA, AUTHENTICITY_KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_generateHmacTag_nullData() {
|
||||
CryptorMicImp v1Cryptor = CryptorMicImp.getInstance();
|
||||
assertThat(v1Cryptor.generateHmacTag(/* data= */ null, AUTHENTICITY_KEY)).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_generateHmacTag_nullKey() {
|
||||
CryptorMicImp v1Cryptor = CryptorMicImp.getInstance();
|
||||
assertThat(v1Cryptor.generateHmacTag(DATA, /* authenticityKey= */ null)).isNull();
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ public final class CryptorTest {
|
|||
assertThat(res2).hasLength(outputSize);
|
||||
assertThat(res1).isNotEqualTo(res2);
|
||||
assertThat(res1)
|
||||
.isEqualTo(CryptorImpV1.computeHkdf(DATA, AUTHENTICITY_KEY, outputSize));
|
||||
.isEqualTo(CryptorMicImp.computeHkdf(DATA, AUTHENTICITY_KEY, outputSize));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
Loading…
Reference in New Issue