1 /*
2  * Copyright (C) 2021 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 package android.devicepolicy.cts;
17 
18 import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
19 import static android.Manifest.permission.NETWORK_SETTINGS;
20 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.testng.Assert.assertThrows;
25 
26 import android.annotation.NonNull;
27 import android.app.admin.flags.Flags;
28 import android.content.Context;
29 import android.net.wifi.WifiManager;
30 import android.os.Build;
31 import android.platform.test.annotations.RequiresFlagsEnabled;
32 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
33 import android.telephony.TelephonyManager;
34 
35 import com.android.bedstead.harrier.BedsteadJUnit4;
36 import com.android.bedstead.harrier.DeviceState;
37 import com.android.bedstead.permissions.annotations.EnsureHasPermission;
38 import com.android.bedstead.harrier.annotations.Postsubmit;
39 import com.android.bedstead.enterprise.annotations.EnsureHasDevicePolicyManagerRoleHolder;
40 import com.android.bedstead.enterprise.annotations.PolicyAppliesTest;
41 import com.android.bedstead.harrier.policies.EnrollmentSpecificId;
42 import com.android.bedstead.nene.TestApis;
43 
44 import org.junit.ClassRule;
45 import org.junit.Rule;
46 import org.junit.rules.RuleChain;
47 import org.junit.rules.TestRule;
48 import org.junit.runner.RunWith;
49 
50 import java.nio.ByteBuffer;
51 import java.security.InvalidKeyException;
52 import java.security.NoSuchAlgorithmException;
53 
54 import javax.crypto.Mac;
55 import javax.crypto.spec.SecretKeySpec;
56 
57 @RunWith(BedsteadJUnit4.class)
58 public final class EnrollmentSpecificIdTest {
59 
60     private static final String ORGANIZATION_ID = "abcxyz123";
61     private static final String DIFFERENT_ORGANIZATION_ID = "xyz";
62 
63     @ClassRule
64     public static final DeviceState sDeviceState = new DeviceState();
65 
66     @Rule
67     public final TestRule mCheckFlagsRule = RuleChain
68             .outerRule(DeviceFlagsValueProvider.createCheckFlagsRule())
69             .around(sDeviceState);
70     private static final Context sContext = TestApis.context().instrumentedContext();
71 
72     @Postsubmit(reason = "New test")
73     @PolicyAppliesTest(policy = EnrollmentSpecificId.class)
emptyOrganizationId_throws()74     public void emptyOrganizationId_throws() {
75         assertThrows(IllegalArgumentException.class,
76                 () -> sDeviceState.dpc().devicePolicyManager().setOrganizationId(""));
77     }
78 
79     @Postsubmit(reason = "New test")
80     @PolicyAppliesTest(policy = EnrollmentSpecificId.class)
reSetOrganizationId_throws()81     public void reSetOrganizationId_throws() {
82         try {
83             sDeviceState.dpc().devicePolicyManager().setOrganizationId(ORGANIZATION_ID);
84 
85             assertThrows(IllegalStateException.class,
86                     () -> sDeviceState.dpc().devicePolicyManager()
87                             .setOrganizationId(DIFFERENT_ORGANIZATION_ID));
88         } finally {
89             TestApis.devicePolicy().clearOrganizationId(sDeviceState.dpc().user());
90         }
91     }
92 
93     /**
94      * This test tests that the platform calculates the ESID according to the specification and
95      * does not, for example, return the same ESID regardless of the managing package.
96      */
97     @Postsubmit(reason = "New test")
98     @PolicyAppliesTest(policy = EnrollmentSpecificId.class)
99     @EnsureHasPermission({READ_PRIVILEGED_PHONE_STATE, NETWORK_SETTINGS, LOCAL_MAC_ADDRESS})
enrollmentSpecificId_CorrectlyCalculated()100     public void enrollmentSpecificId_CorrectlyCalculated() {
101         try {
102             sDeviceState.dpc().devicePolicyManager().setOrganizationId(ORGANIZATION_ID);
103             final String esidFromDpm = sDeviceState.dpc().devicePolicyManager()
104                     .getEnrollmentSpecificId();
105             final String calculatedEsid = calculateEsid(
106                     sDeviceState.dpc().componentName().getPackageName(),
107                     ORGANIZATION_ID);
108 
109             assertThat(esidFromDpm).isEqualTo(calculatedEsid);
110         } finally {
111             TestApis.devicePolicy().clearOrganizationId(sDeviceState.dpc().user());
112         }
113     }
114 
115     /**
116      * New role holder test
117      */
118     @Postsubmit(reason = "New test")
119     @PolicyAppliesTest(policy = EnrollmentSpecificId.class)
120     @EnsureHasPermission({READ_PRIVILEGED_PHONE_STATE, NETWORK_SETTINGS, LOCAL_MAC_ADDRESS})
121     @EnsureHasDevicePolicyManagerRoleHolder
122     @RequiresFlagsEnabled(Flags.FLAG_PERMISSION_MIGRATION_FOR_ZERO_TRUST_IMPL_ENABLED)
enrollmentSpecificId_RoleHolderCanAccess()123     public void enrollmentSpecificId_RoleHolderCanAccess() {
124         try {
125             sDeviceState.dpc().devicePolicyManager().setOrganizationId(ORGANIZATION_ID);
126             String esidFromDpm = sDeviceState.dpc().devicePolicyManager()
127                     .getEnrollmentSpecificId();
128             String esidFromRoleHolder = sDeviceState.dpmRoleHolder().devicePolicyManager()
129                     .getEnrollmentSpecificId();
130 
131             assertThat(esidFromDpm).isEqualTo(esidFromRoleHolder);
132         } finally {
133             TestApis.devicePolicy().clearOrganizationId(sDeviceState.dpc().user());
134         }
135     }
136 
calculateEsid(String profileOwnerPackage, String enterpriseIdString)137     private String calculateEsid(String profileOwnerPackage, String enterpriseIdString) {
138         TelephonyManager telephonyService = sContext.getSystemService(TelephonyManager.class);
139         assertThat(telephonyService).isNotNull();
140 
141         WifiManager wifiManager = sContext.getSystemService(WifiManager.class);
142         assertThat(wifiManager).isNotNull();
143 
144         final byte[] serialNumber = getPaddedHardwareIdentifier(Build.getSerial()).getBytes();
145         final byte[] imei = getPaddedHardwareIdentifier(telephonyService.getImei(0)).getBytes();
146         final byte[] meid = getPaddedHardwareIdentifier(telephonyService.getMeid(0)).getBytes();
147 
148         final byte[] macAddress;
149         final String[] macAddresses = wifiManager.getFactoryMacAddresses();
150         if (macAddresses == null || macAddresses.length == 0) {
151             macAddress = "".getBytes();
152         } else {
153             macAddress = macAddresses[0].getBytes();
154         }
155 
156         final int totalIdentifiersLength = serialNumber.length + imei.length + meid.length
157                 + macAddress.length;
158         final ByteBuffer fixedIdentifiers = ByteBuffer.allocate(totalIdentifiersLength);
159         fixedIdentifiers.put(serialNumber);
160         fixedIdentifiers.put(imei);
161         fixedIdentifiers.put(meid);
162         fixedIdentifiers.put(macAddress);
163 
164         final byte[] dpcPackage = getPaddedProfileOwnerName(profileOwnerPackage).getBytes();
165         final byte[] enterpriseId = getPaddedEnterpriseId(enterpriseIdString).getBytes();
166         final ByteBuffer info = ByteBuffer.allocate(dpcPackage.length + enterpriseId.length);
167         info.put(dpcPackage);
168         info.put(enterpriseId);
169         final byte[] esidBytes = computeHkdf("HMACSHA256", fixedIdentifiers.array(), null,
170                 info.array(), 16);
171         ByteBuffer esidByteBuffer = ByteBuffer.wrap(esidBytes);
172 
173         return encodeBase32(esidByteBuffer.getLong()) + encodeBase32(esidByteBuffer.getLong());
174     }
175 
getPaddedHardwareIdentifier(String hardwareIdentifier)176     private static String getPaddedHardwareIdentifier(String hardwareIdentifier) {
177         if (hardwareIdentifier == null) {
178             hardwareIdentifier = "";
179         }
180         String hwIdentifier = String.format("%16s", hardwareIdentifier);
181         return hwIdentifier.substring(0, 16);
182     }
183 
getPaddedProfileOwnerName(String profileOwnerPackage)184     private static String getPaddedProfileOwnerName(String profileOwnerPackage) {
185         return String.format("%64s", profileOwnerPackage);
186     }
187 
getPaddedEnterpriseId(String enterpriseId)188     private static String getPaddedEnterpriseId(String enterpriseId) {
189         return String.format("%64s", enterpriseId);
190     }
191 
192     // Copied from android.security.identity.Util, here to make sure Enterprise-Specific ID is
193     // calculated according to spec.
194     @NonNull
computeHkdf( @onNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt, @NonNull final byte[] info, int size)195     private static byte[] computeHkdf(
196             @NonNull String macAlgorithm, @NonNull final byte[] ikm, @NonNull final byte[] salt,
197             @NonNull final byte[] info, int size) {
198         Mac mac = null;
199         try {
200             mac = Mac.getInstance(macAlgorithm);
201         } catch (NoSuchAlgorithmException e) {
202             throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
203         }
204         if (size > 255 * mac.getMacLength()) {
205             throw new RuntimeException("size too large");
206         }
207         try {
208             if (salt == null || salt.length == 0) {
209                 // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
210                 // then HKDF uses a salt that is an array of zeros of the same length as the hash
211                 // digest.
212                 mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
213             } else {
214                 mac.init(new SecretKeySpec(salt, macAlgorithm));
215             }
216             byte[] prk = mac.doFinal(ikm);
217             byte[] result = new byte[size];
218             int ctr = 1;
219             int pos = 0;
220             mac.init(new SecretKeySpec(prk, macAlgorithm));
221             byte[] digest = new byte[0];
222             while (true) {
223                 mac.update(digest);
224                 mac.update(info);
225                 mac.update((byte) ctr);
226                 digest = mac.doFinal();
227                 if (pos + digest.length < size) {
228                     System.arraycopy(digest, 0, result, pos, digest.length);
229                     pos += digest.length;
230                     ctr++;
231                 } else {
232                     System.arraycopy(digest, 0, result, pos, size - pos);
233                     break;
234                 }
235             }
236             return result;
237         } catch (InvalidKeyException e) {
238             throw new RuntimeException("Error MACing", e);
239         }
240     }
241 
242     private static final char[] ENCODE = {
243             'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
244             'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
245             'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
246             'Y', 'Z', '2', '3', '4', '5', '6', '7',
247     };
248 
249     private static final char SEPARATOR = '-';
250     private static final int LONG_SIZE = 13;
251     private static final int GROUP_SIZE = 4;
252 
encodeBase32(long input)253     private static String encodeBase32(long input) {
254         final char[] alphabet = ENCODE;
255 
256         /*
257          * Make a character array with room for the separators between each
258          * group.
259          */
260         final char[] encoded = new char[LONG_SIZE + (LONG_SIZE / GROUP_SIZE)];
261 
262         int index = encoded.length;
263         for (int i = 0; i < LONG_SIZE; i++) {
264             /*
265              * Make sure we don't put a separator at the beginning. Since we're
266              * building from the rear of the array, we use (LONG_SIZE %
267              * GROUP_SIZE) to make the odd-size group appear at the end instead
268              * of the beginning.
269              */
270             if (i > 0 && (i % GROUP_SIZE) == (LONG_SIZE % GROUP_SIZE)) {
271                 encoded[--index] = SEPARATOR;
272             }
273 
274             /*
275              * Extract 5 bits of data, then shift it out.
276              */
277             final int group = (int) (input & 0x1F);
278             input >>>= 5;
279 
280             encoded[--index] = alphabet[group];
281         }
282 
283         return String.valueOf(encoded);
284     }
285 }
286