1 /* 2 * Copyright 2016, 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.parser; 18 19 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE; 20 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME; 21 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE; 22 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM; 23 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER; 24 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION; 25 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME; 26 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM; 27 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED; 28 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE; 29 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME; 30 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT; 31 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION; 32 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE; 33 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_USE_MOBILE_DATA; 34 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_ANONYMOUS_IDENTITY; 35 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE; 36 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_DOMAIN; 37 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_EAP_METHOD; 38 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN; 39 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_IDENTITY; 40 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL; 41 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD; 42 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PHASE2_AUTH; 43 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS; 44 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST; 45 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT; 46 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE; 47 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID; 48 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE; 49 import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC; 50 import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED; 51 52 import static com.android.internal.util.Preconditions.checkNotNull; 53 54 import static java.nio.charset.StandardCharsets.UTF_8; 55 56 import android.content.ComponentName; 57 import android.content.Context; 58 import android.content.Intent; 59 import android.nfc.NdefMessage; 60 import android.nfc.NdefRecord; 61 import android.nfc.NfcAdapter; 62 import android.os.Parcelable; 63 import android.os.PersistableBundle; 64 65 import androidx.annotation.Nullable; 66 import androidx.annotation.VisibleForTesting; 67 68 import com.android.managedprovisioning.common.IllegalProvisioningArgumentException; 69 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences; 70 import com.android.managedprovisioning.common.ProvisionLogger; 71 import com.android.managedprovisioning.common.SettingsFacade; 72 import com.android.managedprovisioning.common.StoreUtils; 73 import com.android.managedprovisioning.model.PackageDownloadInfo; 74 import com.android.managedprovisioning.model.ProvisioningParams; 75 import com.android.managedprovisioning.model.WifiInfo; 76 77 import java.io.IOException; 78 import java.io.StringReader; 79 import java.util.IllformedLocaleException; 80 import java.util.Properties; 81 82 83 /** 84 * A parser which parses provisioning data from intent which stores in {@link Properties}. 85 * 86 * <p>It is used to parse an intent contains the extra {@link NfcAdapter.EXTRA_NDEF_MESSAGES}, which 87 * indicates that provisioning was started via Nfc bump. This extra contains an NDEF message, which 88 * contains an NfcRecord with mime type {@link MIME_TYPE_PROVISIONING_NFC}. This record stores a 89 * serialized properties object, which contains the serialized extras described in the next option. 90 * A typical use case would be a programmer application that sends an Nfc bump to start Nfc 91 * provisioning from a programmer device. 92 */ 93 @VisibleForTesting 94 public class PropertiesProvisioningDataParser implements ProvisioningDataParser { 95 96 private final ParserUtils mParserUtils; 97 private final Context mContext; 98 private final ManagedProvisioningSharedPreferences mSharedPreferences; 99 private final SettingsFacade mSettingsFacade; 100 PropertiesProvisioningDataParser(Context context, ParserUtils parserUtils, SettingsFacade settingsFacade)101 PropertiesProvisioningDataParser(Context context, ParserUtils parserUtils, 102 SettingsFacade settingsFacade) { 103 this(context, parserUtils, settingsFacade, 104 new ManagedProvisioningSharedPreferences(context)); 105 } 106 107 @VisibleForTesting PropertiesProvisioningDataParser(Context context, ParserUtils parserUtils, SettingsFacade settingsFacade, ManagedProvisioningSharedPreferences sharedPreferences)108 PropertiesProvisioningDataParser(Context context, ParserUtils parserUtils, 109 SettingsFacade settingsFacade, ManagedProvisioningSharedPreferences sharedPreferences) { 110 mContext = checkNotNull(context); 111 mParserUtils = checkNotNull(parserUtils); 112 mSettingsFacade = checkNotNull(settingsFacade); 113 mSharedPreferences = checkNotNull(sharedPreferences); 114 } 115 116 @Nullable getPropertyFromLongName(Properties properties, String longName)117 private String getPropertyFromLongName(Properties properties, String longName) { 118 if (properties.containsKey(longName)) { 119 return properties.getProperty(longName); 120 } 121 String shortName = ExtrasProvisioningDataParser.getShortExtraNames(longName); 122 if (properties.containsKey(shortName)) { 123 return properties.getProperty(shortName); 124 } 125 return null; 126 } 127 parse(Intent nfcIntent)128 public ProvisioningParams parse(Intent nfcIntent) 129 throws IllegalProvisioningArgumentException { 130 if (!ACTION_NDEF_DISCOVERED.equals(nfcIntent.getAction())) { 131 throw new IllegalProvisioningArgumentException( 132 "Only NFC action is supported in this parser."); 133 } 134 135 ProvisionLogger.logi("Processing Nfc Payload."); 136 NdefRecord firstRecord = getFirstNdefRecord(nfcIntent); 137 if (firstRecord != null) { 138 try { 139 Properties props = new Properties(); 140 props.load(new StringReader(new String(firstRecord.getPayload(), UTF_8))); 141 142 // For parsing non-string parameters. 143 String s = null; 144 145 ProvisioningParams.Builder builder = ProvisioningParams.Builder.builder() 146 .setProvisioningId(mSharedPreferences.incrementAndGetProvisioningId()) 147 .setStartedByTrustedSource(true) 148 .setIsNfc(true) 149 .setProvisioningAction(mParserUtils.extractProvisioningAction( 150 nfcIntent, mSettingsFacade, mContext)) 151 .setDeviceAdminPackageName( 152 getPropertyFromLongName( 153 props, EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME)); 154 if ((s = getPropertyFromLongName( 155 props, EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME)) 156 != null) { 157 builder.setDeviceAdminComponentName(ComponentName.unflattenFromString(s)); 158 } 159 160 // Parse time zone, locale and local time. 161 builder.setTimeZone(getPropertyFromLongName(props, EXTRA_PROVISIONING_TIME_ZONE)) 162 .setLocale(StoreUtils.stringToLocale( 163 getPropertyFromLongName(props, EXTRA_PROVISIONING_LOCALE))); 164 if ((s = getPropertyFromLongName(props, EXTRA_PROVISIONING_LOCAL_TIME)) != null) { 165 builder.setLocalTime(Long.parseLong(s)); 166 } 167 168 // Parse WiFi configuration. 169 builder.setWifiInfo(parseWifiInfoFromProperties(props)) 170 // Parse device admin package download info. 171 .setDeviceAdminDownloadInfo(parsePackageDownloadInfoFromProperties(props)) 172 // Parse EMM customized key-value pairs. 173 // Note: EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE property contains a 174 // Properties object serialized into String. See Properties.store() and 175 // Properties.load() for more details. The property value is optional. 176 .setAdminExtrasBundle(deserializeExtrasBundle(props, 177 EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE)); 178 if ((s = getPropertyFromLongName( 179 props, EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED)) != null) { 180 builder.setLeaveAllSystemAppsEnabled(Boolean.parseBoolean(s)); 181 } 182 if ((s = getPropertyFromLongName( 183 props, EXTRA_PROVISIONING_SKIP_ENCRYPTION)) != null) { 184 builder.setSkipEncryption(Boolean.parseBoolean(s)); 185 } 186 if ((s = getPropertyFromLongName( 187 props, EXTRA_PROVISIONING_USE_MOBILE_DATA)) != null) { 188 builder.setUseMobileData(Boolean.parseBoolean(s)); 189 } 190 if ((s = getPropertyFromLongName( 191 props, EXTRA_PROVISIONING_SENSORS_PERMISSION_GRANT_OPT_OUT)) != null) { 192 builder.setDeviceOwnerPermissionGrantOptOut(Boolean.parseBoolean(s)); 193 } 194 builder.setIsOrganizationOwnedProvisioning(true); 195 // TODO(b/177849035): Remove NFC-specific logic 196 builder.setReturnBeforePolicyCompliance(true); 197 ProvisionLogger.logi("End processing Nfc Payload."); 198 return builder.build(); 199 } catch (IOException e) { 200 throw new IllegalProvisioningArgumentException("Couldn't load payload", e); 201 } catch (NumberFormatException e) { 202 throw new IllegalProvisioningArgumentException("Incorrect numberformat.", e); 203 } catch (IllformedLocaleException e) { 204 throw new IllegalProvisioningArgumentException("Invalid locale.", e); 205 } catch (IllegalArgumentException e) { 206 throw new IllegalProvisioningArgumentException("Invalid parameter found!", e); 207 } catch (NullPointerException e) { 208 throw new IllegalProvisioningArgumentException( 209 "Compulsory parameter not found!", e); 210 } 211 } 212 throw new IllegalProvisioningArgumentException( 213 "Intent does not contain NfcRecord with the correct MIME type."); 214 } 215 216 /** 217 * Parses Wifi configuration from an {@link Properties} and returns the result in 218 * {@link WifiInfo}. 219 */ 220 @Nullable parseWifiInfoFromProperties(Properties props)221 private WifiInfo parseWifiInfoFromProperties(Properties props) { 222 if (getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_SSID) == null) { 223 return null; 224 } 225 WifiInfo.Builder builder = WifiInfo.Builder.builder() 226 .setSsid(getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_SSID)) 227 .setSecurityType(getPropertyFromLongName( 228 props, EXTRA_PROVISIONING_WIFI_SECURITY_TYPE)) 229 .setPassword(getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_PASSWORD)) 230 .setEapMethod(getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_EAP_METHOD)) 231 .setPhase2Auth(getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_PHASE2_AUTH)) 232 .setCaCertificate(getPropertyFromLongName( 233 props, EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE)) 234 .setUserCertificate(getPropertyFromLongName( 235 props, EXTRA_PROVISIONING_WIFI_USER_CERTIFICATE)) 236 .setIdentity(getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_IDENTITY)) 237 .setAnonymousIdentity(getPropertyFromLongName( 238 props, EXTRA_PROVISIONING_WIFI_ANONYMOUS_IDENTITY)) 239 .setDomain(getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_DOMAIN)) 240 .setProxyHost(getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_PROXY_HOST)) 241 .setProxyBypassHosts(getPropertyFromLongName( 242 props, EXTRA_PROVISIONING_WIFI_PROXY_BYPASS)) 243 .setPacUrl(getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_PAC_URL)); 244 // For parsing non-string parameters. 245 String s = null; 246 if ((s = getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_PROXY_PORT)) != null) { 247 builder.setProxyPort(Integer.parseInt(s)); 248 } 249 if ((s = getPropertyFromLongName(props, EXTRA_PROVISIONING_WIFI_HIDDEN)) != null) { 250 builder.setHidden(Boolean.parseBoolean(s)); 251 } 252 253 return builder.build(); 254 } 255 256 /** 257 * Parses device admin package download info from an {@link Properties} and returns the result 258 * in {@link PackageDownloadInfo}. 259 */ 260 @Nullable parsePackageDownloadInfoFromProperties(Properties props)261 private PackageDownloadInfo parsePackageDownloadInfoFromProperties(Properties props) { 262 if (getPropertyFromLongName( 263 props, EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION) == null) { 264 return null; 265 } 266 PackageDownloadInfo.Builder builder = PackageDownloadInfo.Builder.builder() 267 .setLocation(getPropertyFromLongName(props, 268 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION)) 269 .setCookieHeader(getPropertyFromLongName(props, 270 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER)); 271 // For parsing non-string parameters. 272 String s = null; 273 if ((s = getPropertyFromLongName( 274 props, EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE)) != null) { 275 builder.setMinVersion(Integer.parseInt(s)); 276 } 277 if ((s = getPropertyFromLongName( 278 props, EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM)) != null) { 279 builder.setPackageChecksum(StoreUtils.stringToByteArray(s)); 280 } 281 if ((s = getPropertyFromLongName(props, EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM)) 282 != null) { 283 builder.setSignatureChecksum(StoreUtils.stringToByteArray(s)); 284 } 285 return builder.build(); 286 } 287 288 /** 289 * Get a {@link PersistableBundle} from a String property in a Properties object. 290 * @param props the source of the extra 291 * @param extraName key into the Properties object 292 * @return the bundle or {@code null} if there was no property with the given name 293 * @throws IOException if there was an error parsing the propery 294 */ deserializeExtrasBundle(Properties props, String extraName)295 private PersistableBundle deserializeExtrasBundle(Properties props, String extraName) 296 throws IOException { 297 PersistableBundle extrasBundle = null; 298 String serializedExtras = getPropertyFromLongName(props, extraName); 299 if (serializedExtras != null) { 300 Properties extrasProp = new Properties(); 301 extrasProp.load(new StringReader(serializedExtras)); 302 extrasBundle = new PersistableBundle(extrasProp.size()); 303 for (String propName : extrasProp.stringPropertyNames()) { 304 extrasBundle.putString(propName, extrasProp.getProperty(propName)); 305 } 306 } 307 return extrasBundle; 308 } 309 310 /** 311 * @return the first {@link NdefRecord} found with a recognized MIME-type 312 */ getFirstNdefRecord(Intent nfcIntent)313 public static NdefRecord getFirstNdefRecord(Intent nfcIntent) { 314 // Only one first message with NFC_MIME_TYPE is used. 315 final Parcelable[] ndefMessages = nfcIntent.getParcelableArrayExtra( 316 NfcAdapter.EXTRA_NDEF_MESSAGES); 317 if (ndefMessages != null) { 318 for (Parcelable rawMsg : ndefMessages) { 319 NdefMessage msg = (NdefMessage) rawMsg; 320 for (NdefRecord record : msg.getRecords()) { 321 String mimeType = new String(record.getType(), UTF_8); 322 323 if (MIME_TYPE_PROVISIONING_NFC.equals(mimeType)) { 324 return record; 325 } 326 327 // Assume only first record of message is used. 328 break; 329 } 330 } 331 } 332 return null; 333 } 334 } 335