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