Home | History | Annotate | Download | only in parser
      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