Home | History | Annotate | Download | only in managedprovisioning
      1 /*
      2  * Copyright 2014, 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;
     18 
     19 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE;
     20 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCAL_TIME;
     21 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE;
     22 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID;
     23 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN;
     24 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE;
     25 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD;
     26 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST;
     27 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT;
     28 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS;
     29 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL;
     30 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE;
     31 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
     32 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION;
     33 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER;
     34 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM;
     35 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED;
     36 import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
     37 import static java.nio.charset.StandardCharsets.UTF_8;
     38 
     39 import android.content.Intent;
     40 import android.nfc.NdefMessage;
     41 import android.nfc.NdefRecord;
     42 import android.nfc.NfcAdapter;
     43 import android.os.Bundle;
     44 import android.os.Parcelable;
     45 import android.os.PersistableBundle;
     46 import android.text.TextUtils;
     47 import android.util.Base64;
     48 
     49 import java.io.IOException;
     50 import java.io.StringReader;
     51 import java.util.Enumeration;
     52 import java.util.HashMap;
     53 import java.util.IllformedLocaleException;
     54 import java.util.Locale;
     55 import java.util.Properties;
     56 
     57 /**
     58  * This class can initialize a {@link ProvisioningParams} object from an intent.
     59  * A {@link ProvisioningParams} object stores various parameters for the device owner provisioning.
     60  * There are two kinds of intents that can be parsed it into {@link ProvisioningParams}:
     61  *
     62  * <p>
     63  * Intent was received via Nfc.
     64  * The intent contains the extra {@link NfcAdapter.EXTRA_NDEF_MESSAGES}, which indicates that
     65  * provisioning was started via Nfc bump. This extra contains an NDEF message, which contains an
     66  * NfcRecord with mime type {@link MIME_TYPE_PROVISIONING_NFC}. This record stores a serialized
     67  * properties object, which contains the serialized extra's described in the next option.
     68  * A typical use case would be a programmer application that sends an Nfc bump to start Nfc
     69  * provisioning from a programmer device.
     70  *
     71  * <p>
     72  * Intent was received directly.
     73  * The intent contains the extra {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME},
     74  * and may contain {@link #EXTRA_PROVISIONING_TIME_ZONE},
     75  * {@link #EXTRA_PROVISIONING_LOCAL_TIME}, and {@link #EXTRA_PROVISIONING_LOCALE}. A download
     76  * location may be specified in {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}
     77  * together with an optional http cookie header
     78  * {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER} accompanied by the SHA-1
     79  * sum of the target file {@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}.
     80  * Additional information to send through to the device admin may be specified in
     81  * {@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}.
     82  * The boolean {@link #EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED} indicates wheter system
     83  * apps should not be disabled.
     84  * Furthermore a wifi network may be specified in {@link #EXTRA_PROVISIONING_WIFI_SSID}, and if
     85  * applicable {@link #EXTRA_PROVISIONING_WIFI_HIDDEN},
     86  * {@link #EXTRA_PROVISIONING_WIFI_SECURITY_TYPE}, {@link #EXTRA_PROVISIONING_WIFI_PASSWORD},
     87  * {@link #EXTRA_PROVISIONING_WIFI_PROXY_HOST}, {@link #EXTRA_PROVISIONING_WIFI_PROXY_PORT},
     88  * {@link #EXTRA_PROVISIONING_WIFI_PROXY_BYPASS}.
     89  * A typical use case would be the {@link BootReminder} sending the intent after device encryption
     90  * and reboot.
     91  *
     92  * <p>
     93  * Furthermore this class can construct the bundle of extras for the second kind of intent given a
     94  * {@link ProvisioningParams}, and it keeps track of the types of the extras in the
     95  * DEVICE_OWNER_x_EXTRAS, with x the appropriate type.
     96  */
     97 public class MessageParser {
     98     private static final String EXTRA_PROVISIONING_STARTED_BY_NFC  =
     99             "com.android.managedprovisioning.extra.started_by_nfc";
    100 
    101     protected static final String[] DEVICE_OWNER_STRING_EXTRAS = {
    102         EXTRA_PROVISIONING_TIME_ZONE,
    103         EXTRA_PROVISIONING_LOCALE,
    104         EXTRA_PROVISIONING_WIFI_SSID,
    105         EXTRA_PROVISIONING_WIFI_SECURITY_TYPE,
    106         EXTRA_PROVISIONING_WIFI_PASSWORD,
    107         EXTRA_PROVISIONING_WIFI_PROXY_HOST,
    108         EXTRA_PROVISIONING_WIFI_PROXY_BYPASS,
    109         EXTRA_PROVISIONING_WIFI_PAC_URL,
    110         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
    111         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
    112         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER,
    113         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM
    114     };
    115 
    116     protected static final String[] DEVICE_OWNER_LONG_EXTRAS = {
    117         EXTRA_PROVISIONING_LOCAL_TIME
    118     };
    119 
    120     protected static final String[] DEVICE_OWNER_INT_EXTRAS = {
    121         EXTRA_PROVISIONING_WIFI_PROXY_PORT
    122     };
    123 
    124     protected static final String[] DEVICE_OWNER_BOOLEAN_EXTRAS = {
    125         EXTRA_PROVISIONING_WIFI_HIDDEN,
    126         EXTRA_PROVISIONING_STARTED_BY_NFC,
    127         EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED
    128     };
    129 
    130     protected static final String[] DEVICE_OWNER_PERSISTABLE_BUNDLE_EXTRAS = {
    131         EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
    132     };
    133 
    134     public void addProvisioningParamsToBundle(Bundle bundle, ProvisioningParams params) {
    135         bundle.putString(EXTRA_PROVISIONING_TIME_ZONE, params.mTimeZone);
    136         bundle.putString(EXTRA_PROVISIONING_LOCALE, params.getLocaleAsString());
    137         bundle.putString(EXTRA_PROVISIONING_WIFI_SSID, params.mWifiSsid);
    138         bundle.putString(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, params.mWifiSecurityType);
    139         bundle.putString(EXTRA_PROVISIONING_WIFI_PASSWORD, params.mWifiPassword);
    140         bundle.putString(EXTRA_PROVISIONING_WIFI_PROXY_HOST, params.mWifiProxyHost);
    141         bundle.putString(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS, params.mWifiProxyBypassHosts);
    142         bundle.putString(EXTRA_PROVISIONING_WIFI_PAC_URL, params.mWifiPacUrl);
    143         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME,
    144                 params.mDeviceAdminPackageName);
    145         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION,
    146                 params.mDeviceAdminPackageDownloadLocation);
    147         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER,
    148                 params.mDeviceAdminPackageDownloadCookieHeader);
    149         bundle.putString(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM,
    150                 params.getDeviceAdminPackageChecksumAsString());
    151 
    152         bundle.putLong(EXTRA_PROVISIONING_LOCAL_TIME, params.mLocalTime);
    153 
    154         bundle.putInt(EXTRA_PROVISIONING_WIFI_PROXY_PORT, params.mWifiProxyPort);
    155 
    156         bundle.putBoolean(EXTRA_PROVISIONING_WIFI_HIDDEN, params.mWifiHidden);
    157         bundle.putBoolean(EXTRA_PROVISIONING_STARTED_BY_NFC, params.mStartedByNfc);
    158         bundle.putBoolean(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
    159                 params.mLeaveAllSystemAppsEnabled);
    160 
    161         bundle.putParcelable(EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE, params.mAdminExtrasBundle);
    162     }
    163 
    164     public ProvisioningParams parseIntent(Intent intent) throws ParseException {
    165         ProvisionLogger.logi("Processing intent.");
    166         if (intent.hasExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
    167             return parseNfcIntent(intent);
    168         } else {
    169             return parseNonNfcIntent(intent);
    170         }
    171     }
    172 
    173     public ProvisioningParams parseNfcIntent(Intent nfcIntent)
    174         throws ParseException {
    175         ProvisionLogger.logi("Processing Nfc Payload.");
    176         // Only one first message with NFC_MIME_TYPE is used.
    177         for (Parcelable rawMsg : nfcIntent
    178                      .getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
    179             NdefMessage msg = (NdefMessage) rawMsg;
    180 
    181             // Assume only first record of message is used.
    182             NdefRecord firstRecord = msg.getRecords()[0];
    183             String mimeType = new String(firstRecord.getType(), UTF_8);
    184 
    185             if (MIME_TYPE_PROVISIONING_NFC.equals(mimeType)) {
    186                 ProvisioningParams params = parseProperties(new String(firstRecord.getPayload()
    187                                 , UTF_8));
    188                 params.mStartedByNfc = true;
    189                 return params;
    190             }
    191         }
    192         throw new ParseException(
    193                 "Intent does not contain NfcRecord with the correct MIME type.",
    194                 R.string.device_owner_error_general);
    195     }
    196 
    197     // Note: You can't include the EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE in the properties that is
    198     // send over Nfc, because there is no publicly documented conversion from PersistableBundle to
    199     // String.
    200     private ProvisioningParams parseProperties(String data)
    201             throws ParseException {
    202         ProvisioningParams params = new ProvisioningParams();
    203         try {
    204             Properties props = new Properties();
    205             props.load(new StringReader(data));
    206 
    207             String s; // Used for parsing non-Strings.
    208             params.mTimeZone
    209                     = props.getProperty(EXTRA_PROVISIONING_TIME_ZONE);
    210             if ((s = props.getProperty(EXTRA_PROVISIONING_LOCALE)) != null) {
    211                 params.mLocale = stringToLocale(s);
    212             }
    213             params.mWifiSsid = props.getProperty(EXTRA_PROVISIONING_WIFI_SSID);
    214             params.mWifiSecurityType = props.getProperty(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE);
    215             params.mWifiPassword = props.getProperty(EXTRA_PROVISIONING_WIFI_PASSWORD);
    216             params.mWifiProxyHost = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_HOST);
    217             params.mWifiProxyBypassHosts = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS);
    218             params.mWifiPacUrl = props.getProperty(EXTRA_PROVISIONING_WIFI_PAC_URL);
    219             params.mDeviceAdminPackageName
    220                     = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
    221             params.mDeviceAdminPackageDownloadLocation
    222                     = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION);
    223             params.mDeviceAdminPackageDownloadCookieHeader = props.getProperty(
    224                     EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER);
    225             if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM)) != null) {
    226                 params.mDeviceAdminPackageChecksum = stringToByteArray(s);
    227             }
    228 
    229             if ((s = props.getProperty(EXTRA_PROVISIONING_LOCAL_TIME)) != null) {
    230                 params.mLocalTime = Long.parseLong(s);
    231             }
    232 
    233             if ((s = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_PORT)) != null) {
    234                 params.mWifiProxyPort = Integer.parseInt(s);
    235             }
    236 
    237             if ((s = props.getProperty(EXTRA_PROVISIONING_WIFI_HIDDEN)) != null) {
    238                 params.mWifiHidden = Boolean.parseBoolean(s);
    239             }
    240 
    241             if ((s = props.getProperty(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED)) != null) {
    242                 params.mLeaveAllSystemAppsEnabled = Boolean.parseBoolean(s);
    243             }
    244 
    245             checkValidityOfProvisioningParams(params);
    246             return params;
    247         } catch (IOException e) {
    248             throw new ParseException("Couldn't load payload",
    249                     R.string.device_owner_error_general, e);
    250         } catch (NumberFormatException e) {
    251             throw new ParseException("Incorrect numberformat.",
    252                     R.string.device_owner_error_general, e);
    253         } catch (IllformedLocaleException e) {
    254             throw new ParseException("Invalid locale.",
    255                     R.string.device_owner_error_general, e);
    256         }
    257     }
    258 
    259     public ProvisioningParams parseNonNfcIntent(Intent intent)
    260         throws ParseException {
    261         ProvisionLogger.logi("Processing intent.");
    262         ProvisioningParams params = new ProvisioningParams();
    263 
    264         params.mTimeZone = intent.getStringExtra(EXTRA_PROVISIONING_TIME_ZONE);
    265         String localeString = intent.getStringExtra(EXTRA_PROVISIONING_LOCALE);
    266         if (localeString != null) {
    267             params.mLocale = stringToLocale(localeString);
    268         }
    269         params.mWifiSsid = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SSID);
    270         params.mWifiSecurityType = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE);
    271         params.mWifiPassword = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PASSWORD);
    272         params.mWifiProxyHost = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_HOST);
    273         params.mWifiProxyBypassHosts = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS);
    274         params.mWifiPacUrl = intent.getStringExtra(EXTRA_PROVISIONING_WIFI_PAC_URL);
    275         params.mDeviceAdminPackageName
    276                 = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME);
    277         params.mDeviceAdminPackageDownloadLocation
    278                 = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION);
    279         params.mDeviceAdminPackageDownloadCookieHeader = intent.getStringExtra(
    280                 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER);
    281         String hashString = intent.getStringExtra(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM);
    282         if (hashString != null) {
    283             params.mDeviceAdminPackageChecksum = stringToByteArray(hashString);
    284         }
    285 
    286         params.mLocalTime = intent.getLongExtra(EXTRA_PROVISIONING_LOCAL_TIME,
    287                 ProvisioningParams.DEFAULT_LOCAL_TIME);
    288 
    289         params.mWifiProxyPort = intent.getIntExtra(EXTRA_PROVISIONING_WIFI_PROXY_PORT,
    290                 ProvisioningParams.DEFAULT_WIFI_PROXY_PORT);
    291 
    292         params.mWifiHidden = intent.getBooleanExtra(EXTRA_PROVISIONING_WIFI_HIDDEN,
    293                 ProvisioningParams.DEFAULT_WIFI_HIDDEN);
    294         params.mStartedByNfc = intent.getBooleanExtra(EXTRA_PROVISIONING_STARTED_BY_NFC,
    295                 false);
    296         params.mLeaveAllSystemAppsEnabled = intent.getBooleanExtra(
    297                 EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED,
    298                 ProvisioningParams.DEFAULT_LEAVE_ALL_SYSTEM_APPS_ENABLED);
    299 
    300         try {
    301             params.mAdminExtrasBundle = (PersistableBundle) intent.getParcelableExtra(
    302                     EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE);
    303         } catch (ClassCastException e) {
    304             throw new ParseException("Extra " + EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE
    305                     + " must be of type PersistableBundle.",
    306                     R.string.device_owner_error_general, e);
    307         }
    308 
    309         checkValidityOfProvisioningParams(params);
    310         return params;
    311     }
    312 
    313     /**
    314      * Check whether necessary fields are set.
    315      */
    316     private void checkValidityOfProvisioningParams(ProvisioningParams params)
    317         throws ParseException  {
    318         if (TextUtils.isEmpty(params.mDeviceAdminPackageName)) {
    319             throw new ParseException("Must provide the name of the device admin package.",
    320                     R.string.device_owner_error_general);
    321         }
    322         if (!TextUtils.isEmpty(params.mDeviceAdminPackageDownloadLocation)) {
    323             if (params.mDeviceAdminPackageChecksum == null ||
    324                     params.mDeviceAdminPackageChecksum.length == 0) {
    325                 throw new ParseException("Checksum of installer file is required for downloading " +
    326                         "device admin file, but not provided.",
    327                         R.string.device_owner_error_general);
    328             }
    329         }
    330     }
    331 
    332     /**
    333      * Exception thrown when the ProvisioningParams initialization failed completely.
    334      *
    335      * Note: We're using a custom exception to avoid catching subsequent exceptions that might be
    336      * significant.
    337      */
    338     public static class ParseException extends Exception {
    339         private int mErrorMessageId;
    340 
    341         public ParseException(String message, int errorMessageId) {
    342             super(message);
    343             mErrorMessageId = errorMessageId;
    344         }
    345 
    346         public ParseException(String message, int errorMessageId, Throwable t) {
    347             super(message, t);
    348             mErrorMessageId = errorMessageId;
    349         }
    350 
    351         public int getErrorMessageId() {
    352             return mErrorMessageId;
    353         }
    354     }
    355 
    356     public static byte[] stringToByteArray(String s)
    357         throws NumberFormatException {
    358         try {
    359             return Base64.decode(s, Base64.URL_SAFE);
    360         } catch (IllegalArgumentException e) {
    361             throw new NumberFormatException("Incorrect checksum format.");
    362         }
    363     }
    364 
    365     public static Locale stringToLocale(String s)
    366         throws IllformedLocaleException {
    367         return new Locale.Builder().setLanguageTag(s.replace("_", "-")).build();
    368     }
    369 }
    370