1 /*
2  * Copyright 2014, 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.managedprovisioning;
18 
19 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE;
20 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME;
21 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE;
22 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID;
23 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN;
24 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE;
25 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD;
26 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST;
27 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT;
28 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS;
29 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL;
30 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
31 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
32 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
33 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER;
34 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM;
35 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED;
36 import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
37 import static java.nio.charset.StandardCharsets.UTF_8;
38 
39 import android.content.Intent;
40 import android.nfc.NdefMessage;
41 import android.nfc.NdefRecord;
42 import android.nfc.NfcAdapter;
43 import android.os.Bundle;
44 import android.os.Parcelable;
45 import android.os.PersistableBundle;
46 import android.text.TextUtils;
47 import android.util.Base64;
48 
49 import java.io.IOException;
50 import java.io.StringReader;
51 import java.util.Enumeration;
52 import java.util.HashMap;
53 import java.util.IllformedLocaleException;
54 import java.util.Locale;
55 import java.util.Properties;
56 
57 /**
58  * This class can initialize a {@link ProvisioningParams} object from an intent.
59  * A {@link ProvisioningParams} object stores various parameters for the device owner provisioning.
60  * There are two kinds of intents that can be parsed it into {@link ProvisioningParams}:
61  *
62  * <p>
63  * Intent was received via Nfc.
64  * The intent contains the extra {@link NfcAdapter.EXTRA_NDEF_MESSAGES}, which indicates that
65  * provisioning was started via Nfc bump. This extra contains an NDEF message, which contains an
66  * NfcRecord with mime type {@link MIME_TYPE_PROVISIONING_NFC}. This record stores a serialized
67  * properties object, which contains the serialized extra's described in the next option.
68  * A typical use case would be a programmer application that sends an Nfc bump to start Nfc
69  * provisioning from a programmer device.
70  *
71  * <p>
72  * Intent was received directly.
73  * The intent contains the extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME},
74  * and may contain {@link #EXTRA_PROVISIONING_TIME_ZONE},
75  * {@link #EXTRA_PROVISIONING_LOCAL_TIME}, and {@link #EXTRA_PROVISIONING_LOCALE}. A download
76  * location may be specified in {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}
77  * together with an optional http cookie header
78  * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER} accompanied by the SHA-1
79  * sum of the target file {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}.
80  * Additional information to send through to the device admin may be specified in
81  * {@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}.
82  * The boolean {@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED} indicates wheter system
83  * apps should not be disabled.
84  * Furthermore a wifi network may be specified in {@link #EXTRA_PROVISIONING_WIFI_SSID}, and if
85  * applicable {@link #EXTRA_PROVISIONING_WIFI_HIDDEN},
86  * {@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE}, {@link #EXTRA_PROVISIONING_WIFI_PASSWORD},
87  * {@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}, {@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT},
88  * {@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}.
89  * A typical use case would be the {@link BootReminder} sending the intent after device encryption
90  * and reboot.
91  *
92  * <p>
93  * Furthermore this class can construct the bundle of extras for the second kind of intent given a
94  * {@link ProvisioningParams}, and it keeps track of the types of the extras in the
95  * DEVICE_OWNER_x_EXTRAS, with x the appropriate type.
96  */
97 public class MessageParser {
98     private static final String EXTRA_PROVISIONING_STARTED_BY_NFC  =
99             "com.android.managedprovisioning.extra.started_by_nfc";
100 
101     protected static final String[] DEVICE_OWNER_STRING_EXTRAS = {
102         EXTRA_PROVISIONING_TIME_ZONE,
103         EXTRA_PROVISIONING_LOCALE,
104         EXTRA_PROVISIONING_WIFI_SSID,
105         EXTRA_PROVISIONING_WIFI_SECURITY_TYPE,
106         EXTRA_PROVISIONING_WIFI_PASSWORD,
107         EXTRA_PROVISIONING_WIFI_PROXY_HOST,
108         EXTRA_PROVISIONING_WIFI_PROXY_BYPASS,
109         EXTRA_PROVISIONING_WIFI_PAC_URL,
110         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
111         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
112         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER,
113         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM
114     };
115 
116     protected static final String[] DEVICE_OWNER_LONG_EXTRAS = {
117         EXTRA_PROVISIONING_LOCAL_TIME
118     };
119 
120     protected static final String[] DEVICE_OWNER_INT_EXTRAS = {
121         EXTRA_PROVISIONING_WIFI_PROXY_PORT
122     };
123 
124     protected static final String[] DEVICE_OWNER_BOOLEAN_EXTRAS = {
125         EXTRA_PROVISIONING_WIFI_HIDDEN,
126         EXTRA_PROVISIONING_STARTED_BY_NFC,
127         EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED
128     };
129 
130     protected static final String[] DEVICE_OWNER_PERSISTABLE_BUNDLE_EXTRAS = {
131         EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
132     };
133 
addProvisioningParamsToBundle(Bundle bundle, ProvisioningParams params)134     public void addProvisioningParamsToBundle(Bundle bundle, ProvisioningParams params) {
135         bundle.putString(EXTRA_PROVISIONING_TIME_ZONE, params.mTimeZone);
136         bundle.putString(EXTRA_PROVISIONING_LOCALE, params.getLocaleAsString());
137         bundle.putString(EXTRA_PROVISIONING_WIFI_SSID, params.mWifiSsid);
138         bundle.putString(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, params.mWifiSecurityType);
139         bundle.putString(EXTRA_PROVISIONING_WIFI_PASSWORD, params.mWifiPassword);
140         bundle.putString(EXTRA_PROVISIONING_WIFI_PROXY_HOST, params.mWifiProxyHost);
141         bundle.putString(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS, params.mWifiProxyBypassHosts);
142         bundle.putString(EXTRA_PROVISIONING_WIFI_PAC_URL, params.mWifiPacUrl);
143         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
144                 params.mDeviceAdminPackageName);
145         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
146                 params.mDeviceAdminPackageDownloadLocation);
147         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER,
148                 params.mDeviceAdminPackageDownloadCookieHeader);
149         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM,
150                 params.getDeviceAdminPackageChecksumAsString());
151 
152         bundle.putLong(EXTRA_PROVISIONING_LOCAL_TIME, params.mLocalTime);
153 
154         bundle.putInt(EXTRA_PROVISIONING_WIFI_PROXY_PORT, params.mWifiProxyPort);
155 
156         bundle.putBoolean(EXTRA_PROVISIONING_WIFI_HIDDEN, params.mWifiHidden);
157         bundle.putBoolean(EXTRA_PROVISIONING_STARTED_BY_NFC, params.mStartedByNfc);
158         bundle.putBoolean(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
159                 params.mLeaveAllSystemAppsEnabled);
160 
161         bundle.putParcelable(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, params.mAdminExtrasBundle);
162     }
163 
parseIntent(Intent intent)164     public ProvisioningParams parseIntent(Intent intent) throws ParseException {
165         ProvisionLogger.logi("Processing intent.");
166         if (intent.hasExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
167             return parseNfcIntent(intent);
168         } else {
169             return parseNonNfcIntent(intent);
170         }
171     }
172 
parseNfcIntent(Intent nfcIntent)173     public ProvisioningParams parseNfcIntent(Intent nfcIntent)
174         throws ParseException {
175         ProvisionLogger.logi("Processing Nfc Payload.");
176         // Only one first message with NFC_MIME_TYPE is used.
177         for (Parcelable rawMsg : nfcIntent
178                      .getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
179             NdefMessage msg = (NdefMessage) rawMsg;
180 
181             // Assume only first record of message is used.
182             NdefRecord firstRecord = msg.getRecords()[0];
183             String mimeType = new String(firstRecord.getType(), UTF_8);
184 
185             if (MIME_TYPE_PROVISIONING_NFC.equals(mimeType)) {
186                 ProvisioningParams params = parseProperties(new String(firstRecord.getPayload()
187                                 , UTF_8));
188                 params.mStartedByNfc = true;
189                 return params;
190             }
191         }
192         throw new ParseException(
193                 "Intent does not contain NfcRecord with the correct MIME type.",
194                 R.string.device_owner_error_general);
195     }
196 
197     // Note: You can't include the EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE in the properties that is
198     // send over Nfc, because there is no publicly documented conversion from PersistableBundle to
199     // String.
parseProperties(String data)200     private ProvisioningParams parseProperties(String data)
201             throws ParseException {
202         ProvisioningParams params = new ProvisioningParams();
203         try {
204             Properties props = new Properties();
205             props.load(new StringReader(data));
206 
207             String s; // Used for parsing non-Strings.
208             params.mTimeZone
209                     = props.getProperty(EXTRA_PROVISIONING_TIME_ZONE);
210             if ((s = props.getProperty(EXTRA_PROVISIONING_LOCALE)) != null) {
211                 params.mLocale = stringToLocale(s);
212             }
213             params.mWifiSsid = props.getProperty(EXTRA_PROVISIONING_WIFI_SSID);
214             params.mWifiSecurityType = props.getProperty(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE);
215             params.mWifiPassword = props.getProperty(EXTRA_PROVISIONING_WIFI_PASSWORD);
216             params.mWifiProxyHost = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_HOST);
217             params.mWifiProxyBypassHosts = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS);
218             params.mWifiPacUrl = props.getProperty(EXTRA_PROVISIONING_WIFI_PAC_URL);
219             params.mDeviceAdminPackageName
220                     = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
221             params.mDeviceAdminPackageDownloadLocation
222                     = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION);
223             params.mDeviceAdminPackageDownloadCookieHeader = props.getProperty(
224                     EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER);
225             if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM)) != null) {
226                 params.mDeviceAdminPackageChecksum = stringToByteArray(s);
227             }
228 
229             if ((s = props.getProperty(EXTRA_PROVISIONING_LOCAL_TIME)) != null) {
230                 params.mLocalTime = Long.parseLong(s);
231             }
232 
233             if ((s = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_PORT)) != null) {
234                 params.mWifiProxyPort = Integer.parseInt(s);
235             }
236 
237             if ((s = props.getProperty(EXTRA_PROVISIONING_WIFI_HIDDEN)) != null) {
238                 params.mWifiHidden = Boolean.parseBoolean(s);
239             }
240 
241             if ((s = props.getProperty(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED)) != null) {
242                 params.mLeaveAllSystemAppsEnabled = Boolean.parseBoolean(s);
243             }
244 
245             checkValidityOfProvisioningParams(params);
246             return params;
247         } catch (IOException e) {
248             throw new ParseException("Couldn't load payload",
249                     R.string.device_owner_error_general, e);
250         } catch (NumberFormatException e) {
251             throw new ParseException("Incorrect numberformat.",
252                     R.string.device_owner_error_general, e);
253         } catch (IllformedLocaleException e) {
254             throw new ParseException("Invalid locale.",
255                     R.string.device_owner_error_general, e);
256         }
257     }
258 
parseNonNfcIntent(Intent intent)259     public ProvisioningParams parseNonNfcIntent(Intent intent)
260         throws ParseException {
261         ProvisionLogger.logi("Processing intent.");
262         ProvisioningParams params = new ProvisioningParams();
263 
264         params.mTimeZone = intent.getStringExtra(EXTRA_PROVISIONING_TIME_ZONE);
265         String localeString = intent.getStringExtra(EXTRA_PROVISIONING_LOCALE);
266         if (localeString != null) {
267             params.mLocale = stringToLocale(localeString);
268         }
269         params.mWifiSsid = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SSID);
270         params.mWifiSecurityType = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE);
271         params.mWifiPassword = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PASSWORD);
272         params.mWifiProxyHost = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_HOST);
273         params.mWifiProxyBypassHosts = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS);
274         params.mWifiPacUrl = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PAC_URL);
275         params.mDeviceAdminPackageName
276                 = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
277         params.mDeviceAdminPackageDownloadLocation
278                 = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION);
279         params.mDeviceAdminPackageDownloadCookieHeader = intent.getStringExtra(
280                 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER);
281         String hashString = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM);
282         if (hashString != null) {
283             params.mDeviceAdminPackageChecksum = stringToByteArray(hashString);
284         }
285 
286         params.mLocalTime = intent.getLongExtra(EXTRA_PROVISIONING_LOCAL_TIME,
287                 ProvisioningParams.DEFAULT_LOCAL_TIME);
288 
289         params.mWifiProxyPort = intent.getIntExtra(EXTRA_PROVISIONING_WIFI_PROXY_PORT,
290                 ProvisioningParams.DEFAULT_WIFI_PROXY_PORT);
291 
292         params.mWifiHidden = intent.getBooleanExtra(EXTRA_PROVISIONING_WIFI_HIDDEN,
293                 ProvisioningParams.DEFAULT_WIFI_HIDDEN);
294         params.mStartedByNfc = intent.getBooleanExtra(EXTRA_PROVISIONING_STARTED_BY_NFC,
295                 false);
296         params.mLeaveAllSystemAppsEnabled = intent.getBooleanExtra(
297                 EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
298                 ProvisioningParams.DEFAULT_LEAVE_ALL_SYSTEM_APPS_ENABLED);
299 
300         try {
301             params.mAdminExtrasBundle = (PersistableBundle) intent.getParcelableExtra(
302                     EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
303         } catch (ClassCastException e) {
304             throw new ParseException("Extra " + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
305                     + " must be of type PersistableBundle.",
306                     R.string.device_owner_error_general, e);
307         }
308 
309         checkValidityOfProvisioningParams(params);
310         return params;
311     }
312 
313     /**
314      * Check whether necessary fields are set.
315      */
checkValidityOfProvisioningParams(ProvisioningParams params)316     private void checkValidityOfProvisioningParams(ProvisioningParams params)
317         throws ParseException  {
318         if (TextUtils.isEmpty(params.mDeviceAdminPackageName)) {
319             throw new ParseException("Must provide the name of the device admin package.",
320                     R.string.device_owner_error_general);
321         }
322         if (!TextUtils.isEmpty(params.mDeviceAdminPackageDownloadLocation)) {
323             if (params.mDeviceAdminPackageChecksum == null ||
324                     params.mDeviceAdminPackageChecksum.length == 0) {
325                 throw new ParseException("Checksum of installer file is required for downloading " +
326                         "device admin file, but not provided.",
327                         R.string.device_owner_error_general);
328             }
329         }
330     }
331 
332     /**
333      * Exception thrown when the ProvisioningParams initialization failed completely.
334      *
335      * Note: We're using a custom exception to avoid catching subsequent exceptions that might be
336      * significant.
337      */
338     public static class ParseException extends Exception {
339         private int mErrorMessageId;
340 
ParseException(String message, int errorMessageId)341         public ParseException(String message, int errorMessageId) {
342             super(message);
343             mErrorMessageId = errorMessageId;
344         }
345 
ParseException(String message, int errorMessageId, Throwable t)346         public ParseException(String message, int errorMessageId, Throwable t) {
347             super(message, t);
348             mErrorMessageId = errorMessageId;
349         }
350 
getErrorMessageId()351         public int getErrorMessageId() {
352             return mErrorMessageId;
353         }
354     }
355 
stringToByteArray(String s)356     public static byte[] stringToByteArray(String s)
357         throws NumberFormatException {
358         try {
359             return Base64.decode(s, Base64.URL_SAFE);
360         } catch (IllegalArgumentException e) {
361             throw new NumberFormatException("Incorrect checksum format.");
362         }
363     }
364 
stringToLocale(String s)365     public static Locale stringToLocale(String s)
366         throws IllformedLocaleException {
367         return new Locale.Builder().setLanguageTag(s.replace("_", "-")).build();
368     }
369 }
370