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