/* * Copyright 2016, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.managedprovisioning.parser; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE; import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID; import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC; import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED; import static com.android.internal.util.Preconditions.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.nfc.NdefMessage; import android.nfc.NdefRecord; import android.nfc.NfcAdapter; import android.os.Parcelable; import android.os.PersistableBundle; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import com.android.managedprovisioning.ProvisionLogger; import com.android.managedprovisioning.common.IllegalProvisioningArgumentException; import com.android.managedprovisioning.common.Utils; import com.android.managedprovisioning.model.PackageDownloadInfo; import com.android.managedprovisioning.model.ProvisioningParams; import com.android.managedprovisioning.model.WifiInfo; import java.io.IOException; import java.io.StringReader; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.IllformedLocaleException; import java.util.Locale; import java.util.Properties; import java.util.Set; /** * A parser which parses provisioning data from intent which stores in {@link Properties}. * *

It is used to parse an intent contains the extra {@link NfcAdapter.EXTRA_NDEF_MESSAGES}, which * indicates that provisioning was started via Nfc bump. This extra contains an NDEF message, which * contains an NfcRecord with mime type {@link MIME_TYPE_PROVISIONING_NFC}. This record stores a * serialized properties object, which contains the serialized extras described in the next option. * A typical use case would be a programmer application that sends an Nfc bump to start Nfc * provisioning from a programmer device. */ @VisibleForTesting public class PropertiesProvisioningDataParser implements ProvisioningDataParser { private final Utils mUtils; PropertiesProvisioningDataParser(Utils utils) { mUtils = checkNotNull(utils); } public ProvisioningParams parse(Intent nfcIntent, Context context) throws IllegalProvisioningArgumentException { if (!ACTION_NDEF_DISCOVERED.equals(nfcIntent.getAction())) { throw new IllegalProvisioningArgumentException( "Only NFC action is supported in this parser."); } ProvisionLogger.logi("Processing Nfc Payload."); NdefRecord firstRecord = getFirstNdefRecord(nfcIntent); if (firstRecord != null) { try { Properties props = new Properties(); props.load(new StringReader(new String(firstRecord.getPayload(), UTF_8))); // For parsing non-string parameters. String s = null; ProvisioningParams.Builder builder = ProvisioningParams.Builder.builder() .setStartedByTrustedSource(true) .setProvisioningAction(mUtils.mapIntentToDpmAction(nfcIntent)) .setDeviceAdminPackageName(props.getProperty( EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME)); if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME)) != null) { builder.setDeviceAdminComponentName(ComponentName.unflattenFromString(s)); } // Parse time zone, locale and local time. builder.setTimeZone(props.getProperty(EXTRA_PROVISIONING_TIME_ZONE)) .setLocale(MessageParser.stringToLocale( props.getProperty(EXTRA_PROVISIONING_LOCALE))); if ((s = props.getProperty(EXTRA_PROVISIONING_LOCAL_TIME)) != null) { builder.setLocalTime(Long.parseLong(s)); } // Parse WiFi configuration. builder.setWifiInfo(parseWifiInfoFromProperties(props)) // Parse device admin package download info. .setDeviceAdminDownloadInfo(parsePackageDownloadInfoFromProperties(props)) // Parse EMM customized key-value pairs. // Note: EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE property contains a // Properties object serialized into String. See Properties.store() and // Properties.load() for more details. The property value is optional. .setAdminExtrasBundle(deserializeExtrasBundle(props, EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE)); if ((s = props.getProperty(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED)) != null) { builder.setLeaveAllSystemAppsEnabled(Boolean.parseBoolean(s)); } if ((s = props.getProperty(EXTRA_PROVISIONING_SKIP_ENCRYPTION)) != null) { builder.setSkipEncryption(Boolean.parseBoolean(s)); } ProvisionLogger.logi("End processing Nfc Payload."); return builder.build(); } catch (IOException e) { throw new IllegalProvisioningArgumentException("Couldn't load payload", e); } catch (NumberFormatException e) { throw new IllegalProvisioningArgumentException("Incorrect numberformat.", e); } catch (IllformedLocaleException e) { throw new IllegalProvisioningArgumentException("Invalid locale.", e); } catch (IllegalArgumentException e) { throw new IllegalProvisioningArgumentException("Invalid parameter found!", e); } catch (NullPointerException e) { throw new IllegalProvisioningArgumentException( "Compulsory parameter not found!", e); } } throw new IllegalProvisioningArgumentException( "Intent does not contain NfcRecord with the correct MIME type."); } /** * Parses Wifi configuration from an {@link Properties} and returns the result in * {@link WifiInfo}. */ @Nullable private WifiInfo parseWifiInfoFromProperties(Properties props) { if (props.getProperty(EXTRA_PROVISIONING_WIFI_SSID) == null) { return null; } WifiInfo.Builder builder = WifiInfo.Builder.builder() .setSsid(props.getProperty(EXTRA_PROVISIONING_WIFI_SSID)) .setSecurityType(props.getProperty(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE)) .setPassword(props.getProperty(EXTRA_PROVISIONING_WIFI_PASSWORD)) .setProxyHost(props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_HOST)) .setProxyBypassHosts(props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS)) .setPacUrl(props.getProperty(EXTRA_PROVISIONING_WIFI_PAC_URL)); // For parsing non-string parameters. String s = null; if ((s = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_PORT)) != null) { builder.setProxyPort(Integer.parseInt(s)); } if ((s = props.getProperty(EXTRA_PROVISIONING_WIFI_HIDDEN)) != null) { builder.setHidden(Boolean.parseBoolean(s)); } return builder.build(); } /** * Parses device admin package download info from an {@link Properties} and returns the result * in {@link PackageDownloadInfo}. */ @Nullable private PackageDownloadInfo parsePackageDownloadInfoFromProperties(Properties props) { if (props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION) == null) { return null; } PackageDownloadInfo.Builder builder = PackageDownloadInfo.Builder.builder() .setLocation(props.getProperty( EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION)) .setCookieHeader(props.getProperty( EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER)); // For parsing non-string parameters. String s = null; if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE)) != null) { builder.setMinVersion(Integer.parseInt(s)); } if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM)) != null) { // Still support SHA-1 for device admin package hash if we are provisioned by a Nfc // programmer. // TODO: remove once SHA-1 is fully deprecated. builder.setPackageChecksum(mUtils.stringToByteArray(s)) .setPackageChecksumSupportsSha1(true); } if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM)) != null) { builder.setSignatureChecksum(mUtils.stringToByteArray(s)); } return builder.build(); } /** * Get a {@link PersistableBundle} from a String property in a Properties object. * @param props the source of the extra * @param extraName key into the Properties object * @return the bundle or {@code null} if there was no property with the given name * @throws IOException if there was an error parsing the propery */ private PersistableBundle deserializeExtrasBundle(Properties props, String extraName) throws IOException { PersistableBundle extrasBundle = null; String serializedExtras = props.getProperty(extraName); if (serializedExtras != null) { Properties extrasProp = new Properties(); extrasProp.load(new StringReader(serializedExtras)); extrasBundle = new PersistableBundle(extrasProp.size()); for (String propName : extrasProp.stringPropertyNames()) { extrasBundle.putString(propName, extrasProp.getProperty(propName)); } } return extrasBundle; } /** * @return the first {@link NdefRecord} found with a recognized MIME-type */ private NdefRecord getFirstNdefRecord(Intent nfcIntent) { // Only one first message with NFC_MIME_TYPE is used. for (Parcelable rawMsg : nfcIntent.getParcelableArrayExtra( NfcAdapter.EXTRA_NDEF_MESSAGES)) { NdefMessage msg = (NdefMessage) rawMsg; for (NdefRecord record : msg.getRecords()) { String mimeType = new String(record.getType(), UTF_8); if (MIME_TYPE_PROVISIONING_NFC.equals(mimeType)) { return record; } // Assume only first record of message is used. break; } } return null; } }