• 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_USE_MOBILE_DATA;
33 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_HIDDEN;
34 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PAC_URL;
35 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD;
36 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_BYPASS;
37 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_HOST;
38 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PROXY_PORT;
39 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE;
40 import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID;
41 import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
42 import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
43 import static com.android.internal.util.Preconditions.checkNotNull;
44 import static java.nio.charset.StandardCharsets.UTF_8;
45 
46 import android.content.ComponentName;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.nfc.NdefMessage;
50 import android.nfc.NdefRecord;
51 import android.nfc.NfcAdapter;
52 import android.os.Parcelable;
53 import android.os.PersistableBundle;
54 import android.support.annotation.Nullable;
55 import android.support.annotation.VisibleForTesting;
56 import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
57 import com.android.managedprovisioning.common.ProvisionLogger;
58 import com.android.managedprovisioning.common.IllegalProvisioningArgumentException;
59 import com.android.managedprovisioning.common.StoreUtils;
60 import com.android.managedprovisioning.common.Utils;
61 import com.android.managedprovisioning.model.PackageDownloadInfo;
62 import com.android.managedprovisioning.model.ProvisioningParams;
63 import com.android.managedprovisioning.model.WifiInfo;
64 import java.io.IOException;
65 import java.io.StringReader;
66 import java.util.IllformedLocaleException;
67 import java.util.Properties;
68 
69 
70 /**
71  * A parser which parses provisioning data from intent which stores in {@link Properties}.
72  *
73  * <p>It is used to parse an intent contains the extra {@link NfcAdapter.EXTRA_NDEF_MESSAGES}, which
74  * indicates that provisioning was started via Nfc bump. This extra contains an NDEF message, which
75  * contains an NfcRecord with mime type {@link MIME_TYPE_PROVISIONING_NFC}. This record stores a
76  * serialized properties object, which contains the serialized extras described in the next option.
77  * A typical use case would be a programmer application that sends an Nfc bump to start Nfc
78  * provisioning from a programmer device.
79  */
80 @VisibleForTesting
81 public class PropertiesProvisioningDataParser implements ProvisioningDataParser {
82 
83     private final Utils mUtils;
84     private final Context mContext;
85     private final ManagedProvisioningSharedPreferences mSharedPreferences;
86 
PropertiesProvisioningDataParser(Context context, Utils utils)87     PropertiesProvisioningDataParser(Context context, Utils utils) {
88         this(context, utils, new ManagedProvisioningSharedPreferences(context));
89     }
90 
91     @VisibleForTesting
PropertiesProvisioningDataParser(Context context, Utils utils, ManagedProvisioningSharedPreferences sharedPreferences)92     PropertiesProvisioningDataParser(Context context, Utils utils,
93             ManagedProvisioningSharedPreferences sharedPreferences) {
94         mContext = checkNotNull(context);
95         mUtils = checkNotNull(utils);
96         mSharedPreferences = checkNotNull(sharedPreferences);
97     }
98 
parse(Intent nfcIntent)99     public ProvisioningParams parse(Intent nfcIntent)
100             throws IllegalProvisioningArgumentException {
101         if (!ACTION_NDEF_DISCOVERED.equals(nfcIntent.getAction())) {
102             throw new IllegalProvisioningArgumentException(
103                     "Only NFC action is supported in this parser.");
104         }
105 
106         ProvisionLogger.logi("Processing Nfc Payload.");
107         NdefRecord firstRecord = getFirstNdefRecord(nfcIntent);
108         if (firstRecord != null) {
109             try {
110                 Properties props = new Properties();
111                 props.load(new StringReader(new String(firstRecord.getPayload(), UTF_8)));
112 
113                 // For parsing non-string parameters.
114                 String s = null;
115 
116                 ProvisioningParams.Builder builder = ProvisioningParams.Builder.builder()
117                         .setProvisioningId(mSharedPreferences.incrementAndGetProvisioningId())
118                         .setStartedByTrustedSource(true)
119                         .setIsNfc(true)
120                         .setProvisioningAction(mUtils.mapIntentToDpmAction(nfcIntent))
121                         .setDeviceAdminPackageName(props.getProperty(
122                                 EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME));
123                 if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME))
124                         != null) {
125                     builder.setDeviceAdminComponentName(ComponentName.unflattenFromString(s));
126                 }
127 
128                 // Parse time zone, locale and local time.
129                 builder.setTimeZone(props.getProperty(EXTRA_PROVISIONING_TIME_ZONE))
130                         .setLocale(StoreUtils.stringToLocale(
131                                 props.getProperty(EXTRA_PROVISIONING_LOCALE)));
132                 if ((s = props.getProperty(EXTRA_PROVISIONING_LOCAL_TIME)) != null) {
133                     builder.setLocalTime(Long.parseLong(s));
134                 }
135 
136                 // Parse WiFi configuration.
137                 builder.setWifiInfo(parseWifiInfoFromProperties(props))
138                         // Parse device admin package download info.
139                         .setDeviceAdminDownloadInfo(parsePackageDownloadInfoFromProperties(props))
140                         // Parse EMM customized key-value pairs.
141                         // Note: EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE property contains a
142                         // Properties object serialized into String. See Properties.store() and
143                         // Properties.load() for more details. The property value is optional.
144                         .setAdminExtrasBundle(deserializeExtrasBundle(props,
145                                 EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE));
146                 if ((s = props.getProperty(EXTRA_PROVISIONING_LEAVE_ALL_SYSTEM_APPS_ENABLED))
147                         != null) {
148                     builder.setLeaveAllSystemAppsEnabled(Boolean.parseBoolean(s));
149                 }
150                 if ((s = props.getProperty(EXTRA_PROVISIONING_SKIP_ENCRYPTION)) != null) {
151                     builder.setSkipEncryption(Boolean.parseBoolean(s));
152                 }
153                 if ((s = props.getProperty(EXTRA_PROVISIONING_USE_MOBILE_DATA)) != null) {
154                     builder.setUseMobileData(Boolean.parseBoolean(s));
155                 }
156                 ProvisionLogger.logi("End processing Nfc Payload.");
157                 return builder.build();
158             } catch (IOException e) {
159                 throw new IllegalProvisioningArgumentException("Couldn't load payload", e);
160             } catch (NumberFormatException e) {
161                 throw new IllegalProvisioningArgumentException("Incorrect numberformat.", e);
162             } catch (IllformedLocaleException e) {
163                 throw new IllegalProvisioningArgumentException("Invalid locale.", e);
164             } catch (IllegalArgumentException e) {
165                 throw new IllegalProvisioningArgumentException("Invalid parameter found!", e);
166             } catch (NullPointerException e) {
167                 throw new IllegalProvisioningArgumentException(
168                         "Compulsory parameter not found!", e);
169             }
170         }
171         throw new IllegalProvisioningArgumentException(
172                 "Intent does not contain NfcRecord with the correct MIME type.");
173     }
174 
175     /**
176      * Parses Wifi configuration from an {@link Properties} and returns the result in
177      * {@link WifiInfo}.
178      */
179     @Nullable
parseWifiInfoFromProperties(Properties props)180     private WifiInfo parseWifiInfoFromProperties(Properties props) {
181         if (props.getProperty(EXTRA_PROVISIONING_WIFI_SSID) == null) {
182             return null;
183         }
184         WifiInfo.Builder builder = WifiInfo.Builder.builder()
185                 .setSsid(props.getProperty(EXTRA_PROVISIONING_WIFI_SSID))
186                 .setSecurityType(props.getProperty(EXTRA_PROVISIONING_WIFI_SECURITY_TYPE))
187                 .setPassword(props.getProperty(EXTRA_PROVISIONING_WIFI_PASSWORD))
188                 .setProxyHost(props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_HOST))
189                 .setProxyBypassHosts(props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_BYPASS))
190                 .setPacUrl(props.getProperty(EXTRA_PROVISIONING_WIFI_PAC_URL));
191         // For parsing non-string parameters.
192         String s = null;
193         if ((s = props.getProperty(EXTRA_PROVISIONING_WIFI_PROXY_PORT)) != null) {
194             builder.setProxyPort(Integer.parseInt(s));
195         }
196         if ((s = props.getProperty(EXTRA_PROVISIONING_WIFI_HIDDEN)) != null) {
197             builder.setHidden(Boolean.parseBoolean(s));
198         }
199 
200         return builder.build();
201     }
202 
203     /**
204      * Parses device admin package download info from an {@link Properties} and returns the result
205      * in {@link PackageDownloadInfo}.
206      */
207     @Nullable
parsePackageDownloadInfoFromProperties(Properties props)208     private PackageDownloadInfo parsePackageDownloadInfoFromProperties(Properties props) {
209         if (props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION) == null) {
210             return null;
211         }
212         PackageDownloadInfo.Builder builder = PackageDownloadInfo.Builder.builder()
213                 .setLocation(props.getProperty(
214                         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION))
215                 .setCookieHeader(props.getProperty(
216                         EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER));
217         // For parsing non-string parameters.
218         String s = null;
219         if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_MINIMUM_VERSION_CODE)) != null) {
220             builder.setMinVersion(Integer.parseInt(s));
221         }
222         if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM)) != null) {
223             // Still support SHA-1 for device admin package hash if we are provisioned by a Nfc
224             // programmer.
225             // TODO: remove once SHA-1 is fully deprecated.
226             builder.setPackageChecksum(StoreUtils.stringToByteArray(s))
227                     .setPackageChecksumSupportsSha1(true);
228         }
229         if ((s = props.getProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_SIGNATURE_CHECKSUM))
230                 != null) {
231             builder.setSignatureChecksum(StoreUtils.stringToByteArray(s));
232         }
233         return builder.build();
234     }
235 
236     /**
237      * Get a {@link PersistableBundle} from a String property in a Properties object.
238      * @param props the source of the extra
239      * @param extraName key into the Properties object
240      * @return the bundle or {@code null} if there was no property with the given name
241      * @throws IOException if there was an error parsing the propery
242      */
deserializeExtrasBundle(Properties props, String extraName)243     private PersistableBundle deserializeExtrasBundle(Properties props, String extraName)
244             throws IOException {
245         PersistableBundle extrasBundle = null;
246         String serializedExtras = props.getProperty(extraName);
247         if (serializedExtras != null) {
248             Properties extrasProp = new Properties();
249             extrasProp.load(new StringReader(serializedExtras));
250             extrasBundle = new PersistableBundle(extrasProp.size());
251             for (String propName : extrasProp.stringPropertyNames()) {
252                 extrasBundle.putString(propName, extrasProp.getProperty(propName));
253             }
254         }
255         return extrasBundle;
256     }
257 
258     /**
259      * @return the first {@link NdefRecord} found with a recognized MIME-type
260      */
getFirstNdefRecord(Intent nfcIntent)261     public static NdefRecord getFirstNdefRecord(Intent nfcIntent) {
262         // Only one first message with NFC_MIME_TYPE is used.
263         final Parcelable[] ndefMessages = nfcIntent.getParcelableArrayExtra(
264                 NfcAdapter.EXTRA_NDEF_MESSAGES);
265         if (ndefMessages != null) {
266             for (Parcelable rawMsg : ndefMessages) {
267                 NdefMessage msg = (NdefMessage) rawMsg;
268                 for (NdefRecord record : msg.getRecords()) {
269                     String mimeType = new String(record.getType(), UTF_8);
270 
271                     if (MIME_TYPE_PROVISIONING_NFC.equals(mimeType)) {
272                         return record;
273                     }
274 
275                     // Assume only first record of message is used.
276                     break;
277                 }
278             }
279         }
280         return null;
281     }
282 }
283