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 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