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