1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.nearby.presence;
18 
19 import static com.android.server.nearby.presence.PresenceConstants.PRESENCE_UUID_BYTES;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import android.nearby.BroadcastRequest;
24 import android.nearby.DataElement;
25 import android.nearby.PresenceBroadcastRequest;
26 import android.nearby.PresenceCredential;
27 import android.nearby.PrivateCredential;
28 import android.nearby.PublicCredential;
29 
30 import com.android.server.nearby.util.ArrayUtils;
31 import com.android.server.nearby.util.encryption.CryptorMicImp;
32 
33 import org.junit.Before;
34 import org.junit.Test;
35 
36 import java.nio.ByteBuffer;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.List;
41 
42 public class ExtendedAdvertisementTest {
43     private static final int EXTENDED_ADVERTISEMENT_BYTE_LENGTH = 67;
44     private static final int IDENTITY_TYPE = PresenceCredential.IDENTITY_TYPE_PRIVATE;
45     private static final int DATA_TYPE_ACTION = 6;
46     private static final int DATA_TYPE_MODEL_ID = 7;
47     private static final int DATA_TYPE_BLE_ADDRESS = 101;
48     private static final int DATA_TYPE_PUBLIC_IDENTITY = 3;
49     private static final byte[] MODE_ID_DATA =
50             new byte[]{2, 1, 30, 2, 10, -16, 6, 22, 44, -2, -86, -69, -52};
51     private static final byte[] BLE_ADDRESS = new byte[]{124, 4, 56, 60, 120, -29, -90};
52     private static final DataElement MODE_ID_ADDRESS_ELEMENT =
53             new DataElement(DATA_TYPE_MODEL_ID, MODE_ID_DATA);
54     private static final DataElement BLE_ADDRESS_ELEMENT =
55             new DataElement(DATA_TYPE_BLE_ADDRESS, BLE_ADDRESS);
56 
57     private static final byte[] METADATA_ENCRYPTION_KEY =
58             new byte[]{-39, -55, 115, 78, -57, 40, 115, 0, -112, 86, -86, 7, -42, 68, 11, 12};
59     private static final int MEDIUM_TYPE_BLE = 0;
60     private static final byte[] SALT = {2, 3};
61 
62     private static final int PRESENCE_ACTION_1 = 1;
63     private static final int PRESENCE_ACTION_2 = 2;
64     private static final DataElement PRESENCE_ACTION_DE_1 =
65             new DataElement(DATA_TYPE_ACTION, new byte[]{PRESENCE_ACTION_1});
66     private static final DataElement PRESENCE_ACTION_DE_2 =
67             new DataElement(DATA_TYPE_ACTION, new byte[]{PRESENCE_ACTION_2});
68 
69     private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4};
70     private static final byte[] AUTHENTICITY_KEY =
71             new byte[]{-97, 10, 107, -86, 25, 65, -54, -95, -72, 59, 54, 93, 9, 3, -24, -88};
72     private static final byte[] PUBLIC_KEY =
73             new byte[]{
74                     48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72, -50, 61,
75                     66, 0, 4, -56, -39, -92, 69, 0, 52, 23, 67, 83, -14, 75, 52, -14, -5, -41, 48,
76                     -83, 31, 42, -39, 102, -13, 22, -73, -73, 86, 30, -96, -84, -13, 4, 122, 104,
77                     -65, 64, 91, -109, -45, -35, -56, 55, -79, 47, -85, 27, -96, -119, -82, -80,
78                     123, 41, -119, -25, 1, -112, 112
79             };
80     private static final byte[] ENCRYPTED_METADATA_BYTES =
81             new byte[]{
82                     -44, -25, -95, -124, -7, 90, 116, -8, 7, -120, -23, -22, -106, -44, -19, 61,
83                     -18, 39, 29, 78, 108, -11, -39, 85, -30, 64, -99, 102, 65, 37, -42, 114, -37,
84                     88, -112, 8, -75, -53, 23, -16, -104, 67, 49, 48, -53, 73, -109, 44, -23, -11,
85                     -118, -61, -37, -104, 60, 105, 115, 1, 56, -89, -107, -45, -116, -1, -25, 84,
86                     -19, -128, 81, 11, 92, 77, -58, 82, 122, 123, 31, -87, -57, 70, 23, -81, 7, 2,
87                     -114, -83, 74, 124, -68, -98, 47, 91, 9, 48, -67, 41, -7, -97, 78, 66, -65, 58,
88                     -4, -46, -30, -85, -50, 100, 46, -66, -128, 7, 66, 9, 88, 95, 12, -13, 81, -91,
89             };
90     private static final byte[] METADATA_ENCRYPTION_KEY_TAG =
91             new byte[]{-100, 102, -35, -99, 66, -85, -55, -58, -52, 11, -74, 102, 109, -89, 1, -34,
92                     45, 43, 107, -60, 99, -21, 28, 34, 31, -100, -96, 108, 108, -18, 107, 5};
93 
94     private static final String ENCODED_ADVERTISEMENT_ENCRYPTION_INFO =
95             "2091911000DE2A89ED98474AF3E41E48487E8AEBDE90014C18BCB9F9AAC5C11A1BE00A10A5DCD2C49A74BE"
96                     + "BAF0FE72FD5053B9DF8B9976C80BE0DCE8FEE83F1BFA9A89EB176CA48EE4ED5D15C6CDAD6B9E"
97                     + "41187AA6316D7BFD8E454A53971AC00836F7AB0771FF0534050037D49C6AEB18CF9F8590E5CD"
98                     + "EE2FBC330FCDC640C63F0735B7E3F02FE61A0496EF976A158AD3455D";
99     private static final byte[] METADATA_ENCRYPTION_KEY_TAG_2 =
100             new byte[]{-54, -39, 41, 16, 61, 79, -116, 14, 94, 0, 84, 45, 26, -108, 66, -48, 124,
101                     -81, 61, 56, -98, -47, 14, -19, 116, 106, -27, 123, -81, 49, 83, -42};
102 
103     private static final String DEVICE_NAME = "test_device";
104 
105     private static final byte[] SALT_16 =
106             ArrayUtils.stringToBytes("DE2A89ED98474AF3E41E48487E8AEBDE");
107     private static final byte[] AUTHENTICITY_KEY_2 =  ArrayUtils.stringToBytes(
108             "959D2F3CAB8EE4A2DEB0255C03762CF5D39EB919300420E75A089050FB025E20");
109     private static final byte[] METADATA_ENCRYPTION_KEY_2 =  ArrayUtils.stringToBytes(
110             "EF5E9A0867560E52AE1F05FCA7E48D29");
111 
112     private static final DataElement DE1 = new DataElement(571, ArrayUtils.stringToBytes(
113             "537F96FD94E13BE589F0141145CFC0EEC4F86FBDB2"));
114     private static final DataElement DE2 = new DataElement(541, ArrayUtils.stringToBytes(
115             "D301FFB24B5B"));
116     private static final DataElement DE3 = new DataElement(51, ArrayUtils.stringToBytes(
117             "EA95F07C25B75C04E1B2B8731F6A55BA379FB141"));
118     private static final DataElement DE4 = new DataElement(729, ArrayUtils.stringToBytes(
119             "2EFD3101E2311BBB108F0A7503907EAF0C2EAAA60CDA8D33A294C4CEACE0"));
120     private static final DataElement DE5 = new DataElement(411, ArrayUtils.stringToBytes("B0"));
121 
122     private PresenceBroadcastRequest.Builder mBuilder;
123     private PresenceBroadcastRequest.Builder mBuilderCredentialInfo;
124     private PrivateCredential mPrivateCredential;
125     private PrivateCredential mPrivateCredential2;
126 
127     private PublicCredential mPublicCredential;
128     private PublicCredential mPublicCredential2;
129 
130     @Before
setUp()131     public void setUp() {
132         mPrivateCredential =
133                 new PrivateCredential.Builder(
134                         SECRET_ID, AUTHENTICITY_KEY, METADATA_ENCRYPTION_KEY, DEVICE_NAME)
135                         .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
136                         .build();
137         mPrivateCredential2 =
138                 new PrivateCredential.Builder(
139                         SECRET_ID, AUTHENTICITY_KEY_2, METADATA_ENCRYPTION_KEY_2, DEVICE_NAME)
140                         .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
141                         .build();
142         mPublicCredential =
143                 new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY, PUBLIC_KEY,
144                         ENCRYPTED_METADATA_BYTES, METADATA_ENCRYPTION_KEY_TAG)
145                         .build();
146         mPublicCredential2 =
147                 new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY_2, PUBLIC_KEY,
148                         ENCRYPTED_METADATA_BYTES, METADATA_ENCRYPTION_KEY_TAG_2)
149                         .build();
150         mBuilder =
151                 new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
152                         SALT, mPrivateCredential)
153                         .setVersion(BroadcastRequest.PRESENCE_VERSION_V1)
154                         .addAction(PRESENCE_ACTION_1)
155                         .addAction(PRESENCE_ACTION_2)
156                         .addExtendedProperty(new DataElement(DATA_TYPE_BLE_ADDRESS, BLE_ADDRESS))
157                         .addExtendedProperty(new DataElement(DATA_TYPE_MODEL_ID, MODE_ID_DATA));
158 
159         mBuilderCredentialInfo =
160                 new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
161                         SALT_16, mPrivateCredential2)
162                         .setVersion(BroadcastRequest.PRESENCE_VERSION_V1)
163                         .addExtendedProperty(DE1)
164                         .addExtendedProperty(DE2)
165                         .addExtendedProperty(DE3)
166                         .addExtendedProperty(DE4)
167                         .addExtendedProperty(DE5);
168     }
169 
170     @Test
test_createFromRequest()171     public void test_createFromRequest() {
172         ExtendedAdvertisement originalAdvertisement = ExtendedAdvertisement.createFromRequest(
173                 mBuilder.build());
174 
175         assertThat(originalAdvertisement.getActions())
176                 .containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2);
177         assertThat(originalAdvertisement.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY);
178         assertThat(originalAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE);
179         assertThat(originalAdvertisement.getVersion()).isEqualTo(
180                 BroadcastRequest.PRESENCE_VERSION_V1);
181         assertThat(originalAdvertisement.getSalt()).isEqualTo(SALT);
182         assertThat(originalAdvertisement.getDataElements())
183                 .containsExactly(PRESENCE_ACTION_DE_1, PRESENCE_ACTION_DE_2,
184                         MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT);
185         assertThat(originalAdvertisement.getLength()).isEqualTo(EXTENDED_ADVERTISEMENT_BYTE_LENGTH);
186     }
187 
188     @Test
test_createFromRequest_credentialInfo()189     public void test_createFromRequest_credentialInfo() {
190         ExtendedAdvertisement originalAdvertisement = ExtendedAdvertisement.createFromRequest(
191                 mBuilderCredentialInfo.build());
192 
193         assertThat(originalAdvertisement.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY_2);
194         assertThat(originalAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE);
195         assertThat(originalAdvertisement.getVersion()).isEqualTo(
196                 BroadcastRequest.PRESENCE_VERSION_V1);
197         assertThat(originalAdvertisement.getSalt()).isEqualTo(SALT_16);
198         assertThat(originalAdvertisement.getDataElements())
199                 .containsExactly(DE1, DE2, DE3, DE4, DE5);
200     }
201 
202     @Test
test_createFromRequest_encodeAndDecode()203     public void test_createFromRequest_encodeAndDecode() {
204         ExtendedAdvertisement originalAdvertisement = ExtendedAdvertisement.createFromRequest(
205                 mBuilder.build());
206         byte[] generatedBytes = originalAdvertisement.toBytes();
207         ExtendedAdvertisement newAdvertisement =
208                 ExtendedAdvertisement.fromBytes(generatedBytes, mPublicCredential);
209 
210         assertThat(newAdvertisement.getActions())
211                 .containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2);
212         assertThat(newAdvertisement.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY);
213         assertThat(newAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE);
214         assertThat(newAdvertisement.getLength()).isEqualTo(EXTENDED_ADVERTISEMENT_BYTE_LENGTH);
215         assertThat(newAdvertisement.getVersion()).isEqualTo(
216                 BroadcastRequest.PRESENCE_VERSION_V1);
217         assertThat(newAdvertisement.getSalt()).isEqualTo(SALT);
218         assertThat(newAdvertisement.getDataElements())
219                 .containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT,
220                         PRESENCE_ACTION_DE_1, PRESENCE_ACTION_DE_2);
221     }
222 
223     @Test
test_createFromRequest_invalidParameter()224     public void test_createFromRequest_invalidParameter() {
225         // invalid version
226         mBuilder.setVersion(BroadcastRequest.PRESENCE_VERSION_V0);
227         assertThat(ExtendedAdvertisement.createFromRequest(mBuilder.build())).isNull();
228 
229         // invalid salt
230         PresenceBroadcastRequest.Builder builder =
231                 new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
232                         new byte[]{1, 2, 3}, mPrivateCredential)
233                         .setVersion(BroadcastRequest.PRESENCE_VERSION_V1)
234                         .addAction(PRESENCE_ACTION_1);
235         assertThat(ExtendedAdvertisement.createFromRequest(builder.build())).isNull();
236 
237         // invalid identity
238         PrivateCredential privateCredential =
239                 new PrivateCredential.Builder(SECRET_ID,
240                         AUTHENTICITY_KEY, new byte[]{1, 2, 3, 4}, DEVICE_NAME)
241                         .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE)
242                         .build();
243         PresenceBroadcastRequest.Builder builder2 =
244                 new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE),
245                         new byte[]{1, 2, 3}, privateCredential)
246                         .setVersion(BroadcastRequest.PRESENCE_VERSION_V1)
247                         .addAction(PRESENCE_ACTION_1);
248         assertThat(ExtendedAdvertisement.createFromRequest(builder2.build())).isNull();
249     }
250 
251     @Test
test_toBytesSalt()252     public void test_toBytesSalt() throws Exception {
253         ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build());
254         assertThat(adv.toBytes()).isEqualTo(getExtendedAdvertisementByteArray());
255     }
256 
257     @Test
test_fromBytesSalt()258     public void test_fromBytesSalt() throws Exception {
259         byte[] originalBytes = getExtendedAdvertisementByteArray();
260         ExtendedAdvertisement adv =
261                 ExtendedAdvertisement.fromBytes(originalBytes, mPublicCredential);
262 
263         assertThat(adv.getActions())
264                 .containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2);
265         assertThat(adv.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY);
266         assertThat(adv.getIdentityType()).isEqualTo(IDENTITY_TYPE);
267         assertThat(adv.getLength()).isEqualTo(EXTENDED_ADVERTISEMENT_BYTE_LENGTH);
268         assertThat(adv.getVersion()).isEqualTo(
269                 BroadcastRequest.PRESENCE_VERSION_V1);
270         assertThat(adv.getSalt()).isEqualTo(SALT);
271         assertThat(adv.getDataElements())
272                 .containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT,
273                         PRESENCE_ACTION_DE_1, PRESENCE_ACTION_DE_2);
274     }
275 
276     @Test
test_toBytesCredentialElement()277     public void test_toBytesCredentialElement() {
278         ExtendedAdvertisement adv =
279                 ExtendedAdvertisement.createFromRequest(mBuilderCredentialInfo.build());
280         assertThat(ArrayUtils.bytesToStringUppercase(adv.toBytes())).isEqualTo(
281                 ENCODED_ADVERTISEMENT_ENCRYPTION_INFO);
282     }
283 
284     @Test
test_fromBytesCredentialElement()285     public void test_fromBytesCredentialElement() {
286         ExtendedAdvertisement adv =
287                 ExtendedAdvertisement.fromBytes(
288                         ArrayUtils.stringToBytes(ENCODED_ADVERTISEMENT_ENCRYPTION_INFO),
289                         mPublicCredential2);
290         assertThat(adv.getIdentity()).isEqualTo(METADATA_ENCRYPTION_KEY_2);
291         assertThat(adv.getIdentityType()).isEqualTo(IDENTITY_TYPE);
292         assertThat(adv.getVersion()).isEqualTo(BroadcastRequest.PRESENCE_VERSION_V1);
293         assertThat(adv.getSalt()).isEqualTo(SALT_16);
294         assertThat(adv.getDataElements()).containsExactly(DE1, DE2, DE3, DE4, DE5);
295     }
296 
297     @Test
test_fromBytes_metadataTagNotMatched_fail()298     public void test_fromBytes_metadataTagNotMatched_fail() throws Exception {
299         byte[] originalBytes = getExtendedAdvertisementByteArray();
300         PublicCredential credential =
301                 new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY, PUBLIC_KEY,
302                         ENCRYPTED_METADATA_BYTES,
303                         new byte[]{113, 90, -55, 73, 25, -9, 55, -44, 102, 44, 81, -68, 101, 21, 32,
304                                 92, -107, 3, 108, 90, 28, -73, 16, 49, -95, -121, 8, -45, -27, 16,
305                                 6, 108})
306                         .build();
307         ExtendedAdvertisement adv =
308                 ExtendedAdvertisement.fromBytes(originalBytes, credential);
309         assertThat(adv).isNull();
310     }
311 
312     @Test
test_toString()313     public void test_toString() {
314         ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build());
315         assertThat(adv.toString()).isEqualTo("ExtendedAdvertisement:"
316                 + "<VERSION: 1, length: " + EXTENDED_ADVERTISEMENT_BYTE_LENGTH
317                 + ", dataElementCount: 4, identityType: 1, "
318                 + "identity: " + Arrays.toString(METADATA_ENCRYPTION_KEY)
319                 + ", salt: [2, 3],"
320                 + " actions: [1, 2]>");
321     }
322 
323     @Test
test_getDataElements_accordingToType()324     public void test_getDataElements_accordingToType() {
325         ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build());
326         List<DataElement> dataElements = new ArrayList<>();
327 
328         dataElements.add(BLE_ADDRESS_ELEMENT);
329         assertThat(adv.getDataElements(DATA_TYPE_BLE_ADDRESS)).isEqualTo(dataElements);
330         assertThat(adv.getDataElements(DATA_TYPE_PUBLIC_IDENTITY)).isEmpty();
331     }
332 
getExtendedAdvertisementByteArray()333     private static byte[] getExtendedAdvertisementByteArray() throws Exception {
334         CryptorMicImp cryptor = CryptorMicImp.getInstance();
335         ByteBuffer buffer = ByteBuffer.allocate(EXTENDED_ADVERTISEMENT_BYTE_LENGTH);
336         buffer.put((byte) 0b00100000); // Header V1
337         buffer.put(
338                 (byte) (EXTENDED_ADVERTISEMENT_BYTE_LENGTH - 2)); // Section header (section length)
339 
340         // Salt data
341         // Salt header: length 2, type 0
342         byte[] saltBytes = ArrayUtils.concatByteArrays(new byte[]{(byte) 0b00100000}, SALT);
343         buffer.put(saltBytes);
344         // Identity header: length 16, type 1 (private identity)
345         byte[] identityHeader = new byte[]{(byte) 0b10010000, (byte) 0b00000001};
346         buffer.put(identityHeader);
347 
348         ByteBuffer deBuffer = ByteBuffer.allocate(28);
349         // Ble address header: length 7, type 102
350         deBuffer.put(new byte[]{(byte) 0b10000111, (byte) 0b01100101});
351         // Ble address data
352         deBuffer.put(BLE_ADDRESS);
353         // model id header: length 13, type 7
354         deBuffer.put(new byte[]{(byte) 0b10001101, (byte) 0b00000111});
355         // model id data
356         deBuffer.put(MODE_ID_DATA);
357         // Action1 header: length 1, type 6
358         deBuffer.put(new byte[]{(byte) 0b00010110});
359         // Action1 data
360         deBuffer.put((byte) PRESENCE_ACTION_1);
361         // Action2 header: length 1, type 6
362         deBuffer.put(new byte[]{(byte) 0b00010110});
363         // Action2 data
364         deBuffer.put((byte) PRESENCE_ACTION_2);
365         byte[] deBytes = deBuffer.array();
366         byte[] nonce = CryptorMicImp.generateAdvNonce(SALT);
367         byte[] ciphertext =
368                 cryptor.encrypt(
369                         ArrayUtils.concatByteArrays(METADATA_ENCRYPTION_KEY, deBytes),
370                         nonce, AUTHENTICITY_KEY);
371         buffer.put(ciphertext);
372 
373         byte[] dataToSign = ArrayUtils.concatByteArrays(
374                 PRESENCE_UUID_BYTES, /* UUID */
375                 new byte[]{(byte) 0b00100000}, /* header */
376                 new byte[]{(byte) (EXTENDED_ADVERTISEMENT_BYTE_LENGTH - 2)} /* sectionHeader */,
377                 saltBytes, /* salt */
378                 nonce, identityHeader, ciphertext);
379         byte[] mic = cryptor.sign(dataToSign, AUTHENTICITY_KEY);
380         buffer.put(mic);
381 
382         return buffer.array();
383     }
384 }
385