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