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