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