• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
PropertiesProvisioningDataParser(Utils utils)89     PropertiesProvisioningDataParser(Utils utils) {
90         mUtils = checkNotNull(utils);
91     }
92 
parse(Intent nfcIntent, Context context)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
parseWifiInfoFromProperties(Properties props)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
parsePackageDownloadInfoFromProperties(Properties props)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      */
deserializeExtrasBundle(Properties props, String extraName)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      */
getFirstNdefRecord(Intent nfcIntent)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