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