[nearby] Update encoding and decoding according to the spec
Test: w/ MainlineTestApp
Fix: 290294482
Ignore-AOSP-First: nearby_not_in_aosp_yet
(cherry picked from https://googleplex-android-review.googlesource.com/q/17a0803df0c82377ed4afda50c24d59b4a814002)
Merged-In: Ie50d3fae62af6f637583216e3dae5841c1214aa3
Change-Id: Ie50d3fae62af6f637583216e3dae5841c1214aa3
diff --git a/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java
index 34a7514..c2304cc 100644
--- a/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java
+++ b/nearby/service/java/com/android/server/nearby/presence/ExtendedAdvertisement.java
@@ -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 @@
* 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 @@
}
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 @@
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 @@
}
/** 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 @@
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;
}
}
diff --git a/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
index 54264f7..50dada2 100644
--- a/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
+++ b/nearby/service/java/com/android/server/nearby/presence/PresenceConstants.java
@@ -18,10 +18,15 @@
import android.os.ParcelUuid;
+import com.android.server.nearby.util.ArrayUtils;
+
/**
* 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 ParcelUuid PRESENCE_UUID =
ParcelUuid.fromString("0000fcf1-0000-1000-8000-00805f9b34fb");
diff --git a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
index 355f7cf..9dd07a4 100644
--- a/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
+++ b/nearby/service/java/com/android/server/nearby/provider/BleDiscoveryProvider.java
@@ -45,7 +45,6 @@
import com.android.server.nearby.presence.ExtendedAdvertisement;
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;
@@ -133,10 +132,6 @@
.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);
}
@@ -284,17 +279,13 @@
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;
}
}
}
diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java
index 895df69..3f00a42 100644
--- a/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java
+++ b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java
@@ -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.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 @@
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 @@
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 @@
-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 @@
.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 @@
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 @@
.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 @@
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 @@
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();
}