• 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 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 
PropertiesProvisioningDataParser(Context context, Utils utils)86     PropertiesProvisioningDataParser(Context context, Utils utils) {
87         this(context, utils, new ManagedProvisioningSharedPreferences(context));
88     }
89 
90     @VisibleForTesting
PropertiesProvisioningDataParser(Context context, Utils utils, ManagedProvisioningSharedPreferences sharedPreferences)91     PropertiesProvisioningDataParser(Context context, Utils utils,
92             ManagedProvisioningSharedPreferences sharedPreferences) {
93         mContext = checkNotNull(context);
94         mUtils = checkNotNull(utils);
95         mSharedPreferences = checkNotNull(sharedPreferences);
96     }
97 
parse(Intent nfcIntent)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
parseWifiInfoFromProperties(Properties props)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
parsePackageDownloadInfoFromProperties(Properties props)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      */
deserializeExtrasBundle(Properties props, String extraName)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      */
getFirstNdefRecord(Intent nfcIntent)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