1 /*
2  * Copyright (C) 2020 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.devicepolicy;
18 
19 import android.content.Context;
20 import android.content.pm.VerifierDeviceIdentity;
21 import android.net.wifi.WifiManager;
22 import android.os.Build;
23 import android.security.identity.Util;
24 import android.telephony.TelephonyManager;
25 import android.text.TextUtils;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.util.Preconditions;
29 
30 import java.nio.ByteBuffer;
31 
32 class EnterpriseSpecificIdCalculator {
33     private static final int PADDED_HW_ID_LENGTH = 16;
34     private static final int PADDED_PROFILE_OWNER_LENGTH = 64;
35     private static final int PADDED_ENTERPRISE_ID_LENGTH = 64;
36     private static final int ESID_LENGTH = 16;
37 
38     private final String mImei;
39     private final String mMeid;
40     private final String mSerialNumber;
41     private final String mMacAddress;
42 
43     @VisibleForTesting
EnterpriseSpecificIdCalculator(String imei, String meid, String serialNumber, String macAddress)44     EnterpriseSpecificIdCalculator(String imei, String meid, String serialNumber,
45             String macAddress) {
46         mImei = imei;
47         mMeid = meid;
48         mSerialNumber = serialNumber;
49         mMacAddress = macAddress;
50     }
51 
EnterpriseSpecificIdCalculator(Context context)52     EnterpriseSpecificIdCalculator(Context context) {
53         TelephonyManager telephonyService = context.getSystemService(TelephonyManager.class);
54         Preconditions.checkState(telephonyService != null, "Unable to access telephony service");
55 
56         String imei;
57         try {
58             imei = telephonyService.getImei(0);
59         } catch (UnsupportedOperationException doesNotSupportGms) {
60             // Instead of catching the exception, we could check for FEATURE_TELEPHONY_GSM.
61             // However that runs the risk of changing a device's existing ESID if on these devices
62             // telephonyService.getImei() actually returns non-null even when the device does not
63             // declare FEATURE_TELEPHONY_GSM.
64             imei = null;
65         }
66         mImei = imei;
67         String meid;
68         try {
69             meid = telephonyService.getMeid(0);
70         } catch (UnsupportedOperationException doesNotSupportCdma) {
71             // Instead of catching the exception, we could check for FEATURE_TELEPHONY_CDMA.
72             // However that runs the risk of changing a device's existing ESID if on these devices
73             // telephonyService.getMeid() actually returns non-null even when the device does not
74             // declare FEATURE_TELEPHONY_CDMA.
75             meid = null;
76         }
77         mMeid = meid;
78         mSerialNumber = Build.getSerial();
79         WifiManager wifiManager = context.getSystemService(WifiManager.class);
80         Preconditions.checkState(wifiManager != null, "Unable to access WiFi service");
81         final String[] macAddresses = wifiManager.getFactoryMacAddresses();
82         if (macAddresses == null || macAddresses.length == 0) {
83             mMacAddress = "";
84         } else {
85             mMacAddress = macAddresses[0];
86         }
87     }
88 
getPaddedTruncatedString(String input, int maxLength)89     private static String getPaddedTruncatedString(String input, int maxLength) {
90         final String paddedValue = String.format("%" + maxLength + "s", input);
91         return paddedValue.substring(0, maxLength);
92     }
93 
getPaddedHardwareIdentifier(String hardwareIdentifier)94     private static String getPaddedHardwareIdentifier(String hardwareIdentifier) {
95         if (hardwareIdentifier == null) {
96             hardwareIdentifier = "";
97         }
98         return getPaddedTruncatedString(hardwareIdentifier, PADDED_HW_ID_LENGTH);
99     }
100 
getPaddedImei()101     String getPaddedImei() {
102         return getPaddedHardwareIdentifier(mImei);
103     }
104 
getPaddedMeid()105     String getPaddedMeid() {
106         return getPaddedHardwareIdentifier(mMeid);
107     }
108 
getPaddedSerialNumber()109     String getPaddedSerialNumber() {
110         return getPaddedHardwareIdentifier(mSerialNumber);
111     }
112 
getPaddedProfileOwnerName(String profileOwnerPackage)113     String getPaddedProfileOwnerName(String profileOwnerPackage) {
114         return getPaddedTruncatedString(profileOwnerPackage, PADDED_PROFILE_OWNER_LENGTH);
115     }
116 
getPaddedEnterpriseId(String enterpriseId)117     String getPaddedEnterpriseId(String enterpriseId) {
118         return getPaddedTruncatedString(enterpriseId, PADDED_ENTERPRISE_ID_LENGTH);
119     }
120 
121     /**
122      * Calculates the ESID.
123      * @param profileOwnerPackage Package of the Device Policy Client that manages the device/
124      *                            profile. May not be null.
125      * @param enterpriseIdString The identifier for the enterprise in which the device/profile is
126      *                           being enrolled. This parameter may not be empty, but may be null.
127      *                           If called with {@code null}, will calculate an ESID with empty
128      *                           Enterprise ID.
129      */
calculateEnterpriseId(String profileOwnerPackage, String enterpriseIdString)130     public String calculateEnterpriseId(String profileOwnerPackage, String enterpriseIdString) {
131         Preconditions.checkArgument(!TextUtils.isEmpty(profileOwnerPackage),
132                 "owner package must be specified.");
133 
134         Preconditions.checkArgument(enterpriseIdString == null || !enterpriseIdString.isEmpty(),
135                 "enterprise ID must either be null or non-empty.");
136 
137         if (enterpriseIdString == null) {
138             enterpriseIdString = "";
139         }
140 
141         final byte[] serialNumber = getPaddedSerialNumber().getBytes();
142         final byte[] imei = getPaddedImei().getBytes();
143         final byte[] meid = getPaddedMeid().getBytes();
144         final byte[] macAddress = mMacAddress.getBytes();
145         final int totalIdentifiersLength = serialNumber.length + imei.length + meid.length
146                 + macAddress.length;
147         final ByteBuffer fixedIdentifiers = ByteBuffer.allocate(totalIdentifiersLength);
148         fixedIdentifiers.put(serialNumber);
149         fixedIdentifiers.put(imei);
150         fixedIdentifiers.put(meid);
151         fixedIdentifiers.put(macAddress);
152 
153         final byte[] dpcPackage = getPaddedProfileOwnerName(profileOwnerPackage).getBytes();
154         final byte[] enterpriseId = getPaddedEnterpriseId(enterpriseIdString).getBytes();
155         final ByteBuffer info = ByteBuffer.allocate(dpcPackage.length + enterpriseId.length);
156         info.put(dpcPackage);
157         info.put(enterpriseId);
158         final byte[] esidBytes = Util.computeHkdf("HMACSHA256", fixedIdentifiers.array(), null,
159                 info.array(), ESID_LENGTH);
160         ByteBuffer esidByteBuffer = ByteBuffer.wrap(esidBytes);
161 
162         VerifierDeviceIdentity firstId = new VerifierDeviceIdentity(esidByteBuffer.getLong());
163         VerifierDeviceIdentity secondId = new VerifierDeviceIdentity(esidByteBuffer.getLong());
164         return firstId.toString() + secondId.toString();
165     }
166 }
167