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