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