/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi.util; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.InetAddresses; import android.net.IpConfiguration; import android.net.IpConfiguration.IpAssignment; import android.net.IpConfiguration.ProxySettings; import android.net.LinkAddress; import android.net.MacAddress; import android.net.ProxyInfo; import android.net.RouteInfo; import android.net.StaticIpConfiguration; import android.net.Uri; import android.net.wifi.SecurityParams; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; import android.net.wifi.WifiEnterpriseConfig; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.util.SparseIntArray; import com.android.modules.utils.build.SdkLevel; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; /** * Utils for manipulating XML data. This is essentially a wrapper over XmlUtils provided by core. * The utility provides methods to write/parse section headers and write/parse values. * This utility is designed for formatting the XML into the following format: * *
* * * ... * * * * ... * *
*
* * Note: These utility methods are meant to be used for: * 1. Backup/restore wifi network data to/from cloud. * 2. Persisting wifi network data to/from disk. */ public class XmlUtil { private static final String TAG = "WifiXmlUtil"; /** * Ensure that the XML stream is at a start tag or the end of document. * * @throws XmlPullParserException if parsing errors occur. */ private static void gotoStartTag(XmlPullParser in) throws XmlPullParserException, IOException { int type = in.getEventType(); while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { type = in.next(); } } /** * Ensure that the XML stream is at an end tag or the end of document. * * @throws XmlPullParserException if parsing errors occur. */ private static void gotoEndTag(XmlPullParser in) throws XmlPullParserException, IOException { int type = in.getEventType(); while (type != XmlPullParser.END_TAG && type != XmlPullParser.END_DOCUMENT) { type = in.next(); } } /** * Start processing the XML stream at the document header. * * @param in XmlPullParser instance pointing to the XML stream. * @param headerName expected name for the start tag. * @throws XmlPullParserException if parsing errors occur. */ public static void gotoDocumentStart(XmlPullParser in, String headerName) throws XmlPullParserException, IOException { XmlUtilHelper.beginDocument(in, headerName); } /** * Move the XML stream to the next section header or indicate if there are no more sections. * The provided outerDepth is used to find sub sections within that depth. * * Use this to move across sections if the ordering of sections are variable. The returned name * can be used to decide what section is next. * * @param in XmlPullParser instance pointing to the XML stream. * @param headerName An array of one string, used to return the name of the next section. * @param outerDepth Find section within this depth. * @return {@code true} if a next section is found, {@code false} if there are no more sections. * @throws XmlPullParserException if parsing errors occur. */ public static boolean gotoNextSectionOrEnd( XmlPullParser in, String[] headerName, int outerDepth) throws XmlPullParserException, IOException { if (XmlUtilHelper.nextElementWithin(in, outerDepth)) { headerName[0] = in.getName(); return true; } return false; } /** * Move the XML stream to the next section header or indicate if there are no more sections. * If a section, exists ensure that the name matches the provided name. * The provided outerDepth is used to find sub sections within that depth. * * Use this to move across repeated sections until the end. * * @param in XmlPullParser instance pointing to the XML stream. * @param expectedName expected name for the section header. * @param outerDepth Find section within this depth. * @return {@code true} if a next section is found, {@code false} if there are no more sections. * @throws XmlPullParserException if the section header name does not match |expectedName|, * or if parsing errors occur. */ public static boolean gotoNextSectionWithNameOrEnd( XmlPullParser in, String expectedName, int outerDepth) throws XmlPullParserException, IOException { String[] headerName = new String[1]; if (gotoNextSectionOrEnd(in, headerName, outerDepth)) { if (headerName[0].equals(expectedName)) { return true; } throw new XmlPullParserException( "Next section name does not match expected name: " + expectedName); } return false; } /** * Move the XML stream to the next section header and ensure that the name matches the provided * name. * The provided outerDepth is used to find sub sections within that depth. * * Use this to move across sections if the ordering of sections are fixed. * * @param in XmlPullParser instance pointing to the XML stream. * @param expectedName expected name for the section header. * @param outerDepth Find section within this depth. * @throws XmlPullParserException if the section header name does not match |expectedName|, * there are no more sections or if parsing errors occur. */ public static void gotoNextSectionWithName( XmlPullParser in, String expectedName, int outerDepth) throws XmlPullParserException, IOException { if (!gotoNextSectionWithNameOrEnd(in, expectedName, outerDepth)) { throw new XmlPullParserException("Section not found. Expected: " + expectedName); } } /** * Checks if the stream is at the end of a section of values. This moves the stream to next tag * and checks if it finds an end tag at the specified depth. * * @param in XmlPullParser instance pointing to the XML stream. * @param sectionDepth depth of the start tag of this section. Used to match the end tag. * @return {@code true} if a end tag at the provided depth is found, {@code false} otherwise * @throws XmlPullParserException if parsing errors occur. */ public static boolean isNextSectionEnd(XmlPullParser in, int sectionDepth) throws XmlPullParserException, IOException { return !XmlUtilHelper.nextElementWithin(in, sectionDepth); } /** * Read the current value in the XML stream using core XmlUtils and stores the retrieved * value name in the string provided. This method reads the value contained in current start * tag. * Note: Because there could be genuine null values being read from the XML, this method raises * an exception to indicate errors. * * @param in XmlPullParser instance pointing to the XML stream. * @param valueName An array of one string, used to return the name attribute * of the value's tag. * @return value retrieved from the XML stream. * @throws XmlPullParserException if parsing errors occur. */ public static Object readCurrentValue(XmlPullParser in, String[] valueName) throws XmlPullParserException, IOException { Object value = XmlUtilHelper.readValueXml(in, valueName); // XmlUtils.readValue does not always move the stream to the end of the tag. So, move // it to the end tag before returning from here. gotoEndTag(in); return value; } /** * Read the next value in the XML stream using core XmlUtils and ensure that it matches the * provided name. This method moves the stream to the next start tag and reads the value * contained in it. * Note: Because there could be genuine null values being read from the XML, this method raises * an exception to indicate errors. * * @param in XmlPullParser instance pointing to the XML stream. * @return value retrieved from the XML stream. * @throws XmlPullParserException if the value read does not match |expectedName|, * or if parsing errors occur. */ public static Object readNextValueWithName(XmlPullParser in, String expectedName) throws XmlPullParserException, IOException { String[] valueName = new String[1]; XmlUtilHelper.nextElement(in); Object value = readCurrentValue(in, valueName); if (valueName[0].equals(expectedName)) { return value; } throw new XmlPullParserException( "Value not found. Expected: " + expectedName + ", but got: " + valueName[0]); } /** * Write the XML document start with the provided document header name. * * @param out XmlSerializer instance pointing to the XML stream. * @param headerName name for the start tag. */ public static void writeDocumentStart(XmlSerializer out, String headerName) throws IOException { out.startDocument(null, true); out.startTag(null, headerName); } /** * Write the XML document end with the provided document header name. * * @param out XmlSerializer instance pointing to the XML stream. * @param headerName name for the end tag. */ public static void writeDocumentEnd(XmlSerializer out, String headerName) throws IOException { out.endTag(null, headerName); out.endDocument(); } /** * Write a section start header tag with the provided section name. * * @param out XmlSerializer instance pointing to the XML stream. * @param headerName name for the start tag. */ public static void writeNextSectionStart(XmlSerializer out, String headerName) throws IOException { out.startTag(null, headerName); } /** * Write a section end header tag with the provided section name. * * @param out XmlSerializer instance pointing to the XML stream. * @param headerName name for the end tag. */ public static void writeNextSectionEnd(XmlSerializer out, String headerName) throws IOException { out.endTag(null, headerName); } /** * Write the value with the provided name in the XML stream using core XmlUtils. * * @param out XmlSerializer instance pointing to the XML stream. * @param name name of the value. * @param value value to be written. */ public static void writeNextValue(XmlSerializer out, String name, Object value) throws XmlPullParserException, IOException { XmlUtilHelper.writeValueXml(value, name, out); } /** * Utility class to serialize and deserialize {@link WifiConfiguration} object to XML & * vice versa. * This is used by both {@link com.android.server.wifi.WifiConfigStore} & * {@link com.android.server.wifi.WifiBackupRestore} modules. * The |writeConfigurationToXml| has 2 versions, one for backup and one for config store. * There is only 1 version of |parseXmlToConfiguration| for both backup & config store. * The parse method is written so that any element added/deleted in future revisions can * be easily handled. */ public static class WifiConfigurationXmlUtil { /** * List of XML tags corresponding to WifiConfiguration object elements. */ public static final String XML_TAG_SSID = "SSID"; public static final String XML_TAG_BSSID = "BSSID"; public static final String XML_TAG_CONFIG_KEY = "ConfigKey"; public static final String XML_TAG_PRE_SHARED_KEY = "PreSharedKey"; public static final String XML_TAG_WEP_KEYS = "WEPKeys"; public static final String XML_TAG_WEP_TX_KEY_INDEX = "WEPTxKeyIndex"; public static final String XML_TAG_HIDDEN_SSID = "HiddenSSID"; public static final String XML_TAG_REQUIRE_PMF = "RequirePMF"; public static final String XML_TAG_ALLOWED_KEY_MGMT = "AllowedKeyMgmt"; public static final String XML_TAG_ALLOWED_PROTOCOLS = "AllowedProtocols"; public static final String XML_TAG_ALLOWED_AUTH_ALGOS = "AllowedAuthAlgos"; public static final String XML_TAG_ALLOWED_GROUP_CIPHERS = "AllowedGroupCiphers"; public static final String XML_TAG_ALLOWED_PAIRWISE_CIPHERS = "AllowedPairwiseCiphers"; public static final String XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS = "AllowedGroupMgmtCiphers"; public static final String XML_TAG_ALLOWED_SUITE_B_CIPHERS = "AllowedSuiteBCiphers"; public static final String XML_TAG_SHARED = "Shared"; public static final String XML_TAG_STATUS = "Status"; public static final String XML_TAG_FQDN = "FQDN"; public static final String XML_TAG_PROVIDER_FRIENDLY_NAME = "ProviderFriendlyName"; public static final String XML_TAG_LINKED_NETWORKS_LIST = "LinkedNetworksList"; public static final String XML_TAG_DEFAULT_GW_MAC_ADDRESS = "DefaultGwMacAddress"; public static final String XML_TAG_VALIDATED_INTERNET_ACCESS = "ValidatedInternetAccess"; public static final String XML_TAG_NO_INTERNET_ACCESS_EXPECTED = "NoInternetAccessExpected"; public static final String XML_TAG_METERED_HINT = "MeteredHint"; public static final String XML_TAG_METERED_OVERRIDE = "MeteredOverride"; public static final String XML_TAG_USE_EXTERNAL_SCORES = "UseExternalScores"; public static final String XML_TAG_CREATOR_UID = "CreatorUid"; public static final String XML_TAG_CREATOR_NAME = "CreatorName"; public static final String XML_TAG_LAST_UPDATE_UID = "LastUpdateUid"; public static final String XML_TAG_LAST_UPDATE_NAME = "LastUpdateName"; public static final String XML_TAG_LAST_CONNECT_UID = "LastConnectUid"; public static final String XML_TAG_IS_LEGACY_PASSPOINT_CONFIG = "IsLegacyPasspointConfig"; public static final String XML_TAG_ROAMING_CONSORTIUM_OIS = "RoamingConsortiumOIs"; public static final String XML_TAG_RANDOMIZED_MAC_ADDRESS = "RandomizedMacAddress"; public static final String XML_TAG_MAC_RANDOMIZATION_SETTING = "MacRandomizationSetting"; public static final String XML_TAG_CARRIER_ID = "CarrierId"; public static final String XML_TAG_SUBSCRIPTION_ID = "SubscriptionId"; public static final String XML_TAG_IS_AUTO_JOIN = "AutoJoinEnabled"; public static final String XML_TAG_DELETION_PRIORITY = "DeletionPriority"; public static final String XML_TAG_NUM_REBOOTS_SINCE_LAST_USE = "NumRebootsSinceLastUse"; public static final String XML_TAG_IS_TRUSTED = "Trusted"; public static final String XML_TAG_IS_OEM_PAID = "OemPaid"; public static final String XML_TAG_IS_OEM_PRIVATE = "OemPrivate"; public static final String XML_TAG_IS_CARRIER_MERGED = "CarrierMerged"; public static final String XML_TAG_SECURITY_PARAMS_LIST = "SecurityParamsList"; public static final String XML_TAG_SECURITY_PARAMS = "SecurityParams"; public static final String XML_TAG_SECURITY_TYPE = "SecurityType"; public static final String XML_TAG_SAE_IS_H2E_ONLY_MODE = "SaeIsH2eOnlyMode"; public static final String XML_TAG_SAE_IS_PK_ONLY_MODE = "SaeIsPkOnlyMode"; public static final String XML_TAG_IS_ADDED_BY_AUTO_UPGRADE = "IsAddedByAutoUpgrade"; private static final String XML_TAG_IS_MOST_RECENTLY_CONNECTED = "IsMostRecentlyConnected"; /** * Write WepKeys to the XML stream. * WepKeys array is intialized in WifiConfiguration constructor, but all of the elements * are set to null. User may chose to set any one of the key elements in WifiConfiguration. * XmlUtils serialization doesn't handle this array of nulls well . * So, write empty strings if some of the keys are not initialized and null if all of * the elements are empty. */ private static void writeWepKeysToXml(XmlSerializer out, String[] wepKeys) throws XmlPullParserException, IOException { String[] wepKeysToWrite = new String[wepKeys.length]; boolean hasWepKey = false; for (int i = 0; i < wepKeys.length; i++) { if (wepKeys[i] == null) { wepKeysToWrite[i] = new String(); } else { wepKeysToWrite[i] = wepKeys[i]; hasWepKey = true; } } if (hasWepKey) { XmlUtil.writeNextValue(out, XML_TAG_WEP_KEYS, wepKeysToWrite); } else { XmlUtil.writeNextValue(out, XML_TAG_WEP_KEYS, null); } } /** * Write preshared key to the XML stream. * * If encryptionUtil is null or if encryption fails for some reason, the pre-shared * key is stored in plaintext, else the encrypted psk is stored. */ private static void writePreSharedKeyToXml( XmlSerializer out, String preSharedKey, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { EncryptedData encryptedData = null; if (encryptionUtil != null) { if (preSharedKey != null) { encryptedData = encryptionUtil.encrypt(preSharedKey.getBytes()); if (encryptedData == null) { // We silently fail encryption failures! Log.wtf(TAG, "Encryption of preSharedKey failed"); } } } if (encryptedData != null) { XmlUtil.writeNextSectionStart(out, XML_TAG_PRE_SHARED_KEY); EncryptedDataXmlUtil.writeToXml(out, encryptedData); XmlUtil.writeNextSectionEnd(out, XML_TAG_PRE_SHARED_KEY); } else { XmlUtil.writeNextValue(out, XML_TAG_PRE_SHARED_KEY, preSharedKey); } } private static void writeSecurityParamsListToXml( XmlSerializer out, WifiConfiguration configuration) throws XmlPullParserException, IOException { XmlUtil.writeNextSectionStart(out, XML_TAG_SECURITY_PARAMS_LIST); for (SecurityParams params: configuration.getSecurityParamsList()) { XmlUtil.writeNextSectionStart(out, XML_TAG_SECURITY_PARAMS); XmlUtil.writeNextValue( out, XML_TAG_SECURITY_TYPE, params.getSecurityType()); XmlUtil.writeNextValue( out, XML_TAG_SAE_IS_H2E_ONLY_MODE, params.isSaeH2eOnlyMode()); XmlUtil.writeNextValue( out, XML_TAG_SAE_IS_PK_ONLY_MODE, params.isSaePkOnlyMode()); XmlUtil.writeNextValue( out, XML_TAG_IS_ADDED_BY_AUTO_UPGRADE, params.isAddedByAutoUpgrade()); XmlUtil.writeNextSectionEnd(out, XML_TAG_SECURITY_PARAMS); } XmlUtil.writeNextSectionEnd(out, XML_TAG_SECURITY_PARAMS_LIST); } /** * Write the Configuration data elements that are common for backup & config store to the * XML stream. * * @param out XmlSerializer instance pointing to the XML stream. * @param configuration WifiConfiguration object to be serialized. * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. Backup/restore stores * keys unencrypted. */ public static void writeCommonElementsToXml( XmlSerializer out, WifiConfiguration configuration, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { XmlUtil.writeNextValue(out, XML_TAG_CONFIG_KEY, configuration.getKey()); XmlUtil.writeNextValue(out, XML_TAG_SSID, configuration.SSID); writePreSharedKeyToXml(out, configuration.preSharedKey, encryptionUtil); writeWepKeysToXml(out, configuration.wepKeys); XmlUtil.writeNextValue(out, XML_TAG_WEP_TX_KEY_INDEX, configuration.wepTxKeyIndex); XmlUtil.writeNextValue(out, XML_TAG_HIDDEN_SSID, configuration.hiddenSSID); XmlUtil.writeNextValue(out, XML_TAG_REQUIRE_PMF, configuration.requirePmf); XmlUtil.writeNextValue( out, XML_TAG_ALLOWED_KEY_MGMT, configuration.allowedKeyManagement.toByteArray()); XmlUtil.writeNextValue( out, XML_TAG_ALLOWED_PROTOCOLS, configuration.allowedProtocols.toByteArray()); XmlUtil.writeNextValue( out, XML_TAG_ALLOWED_AUTH_ALGOS, configuration.allowedAuthAlgorithms.toByteArray()); XmlUtil.writeNextValue( out, XML_TAG_ALLOWED_GROUP_CIPHERS, configuration.allowedGroupCiphers.toByteArray()); XmlUtil.writeNextValue( out, XML_TAG_ALLOWED_PAIRWISE_CIPHERS, configuration.allowedPairwiseCiphers.toByteArray()); XmlUtil.writeNextValue( out, XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS, configuration.allowedGroupManagementCiphers.toByteArray()); XmlUtil.writeNextValue( out, XML_TAG_ALLOWED_SUITE_B_CIPHERS, configuration.allowedSuiteBCiphers.toByteArray()); XmlUtil.writeNextValue(out, XML_TAG_SHARED, configuration.shared); XmlUtil.writeNextValue(out, XML_TAG_IS_AUTO_JOIN, configuration.allowAutojoin); XmlUtil.writeNextValue( out, XML_TAG_DELETION_PRIORITY, configuration.getDeletionPriority()); XmlUtil.writeNextValue( out, XML_TAG_NUM_REBOOTS_SINCE_LAST_USE, configuration.numRebootsSinceLastUse); writeSecurityParamsListToXml(out, configuration); } /** * Write the Configuration data elements for backup from the provided Configuration to the * XML stream. * Note: This is a subset of the elements serialized for config store. * * @param out XmlSerializer instance pointing to the XML stream. * @param configuration WifiConfiguration object to be serialized. */ public static void writeToXmlForBackup(XmlSerializer out, WifiConfiguration configuration) throws XmlPullParserException, IOException { writeCommonElementsToXml(out, configuration, null); XmlUtil.writeNextValue(out, XML_TAG_METERED_OVERRIDE, configuration.meteredOverride); } /** * Write the Configuration data elements for config store from the provided Configuration * to the XML stream. * * @param out XmlSerializer instance pointing to the XML stream. * @param configuration WifiConfiguration object to be serialized. * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. */ public static void writeToXmlForConfigStore( XmlSerializer out, WifiConfiguration configuration, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { writeCommonElementsToXml(out, configuration, encryptionUtil); XmlUtil.writeNextValue(out, XML_TAG_IS_TRUSTED, configuration.trusted); XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PAID, configuration.oemPaid); XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PRIVATE, configuration.oemPrivate); XmlUtil.writeNextValue(out, XML_TAG_IS_CARRIER_MERGED, configuration.carrierMerged); XmlUtil.writeNextValue(out, XML_TAG_BSSID, configuration.BSSID); XmlUtil.writeNextValue(out, XML_TAG_STATUS, configuration.status); XmlUtil.writeNextValue(out, XML_TAG_FQDN, configuration.FQDN); XmlUtil.writeNextValue( out, XML_TAG_PROVIDER_FRIENDLY_NAME, configuration.providerFriendlyName); XmlUtil.writeNextValue( out, XML_TAG_LINKED_NETWORKS_LIST, configuration.linkedConfigurations); XmlUtil.writeNextValue( out, XML_TAG_DEFAULT_GW_MAC_ADDRESS, configuration.defaultGwMacAddress); XmlUtil.writeNextValue( out, XML_TAG_VALIDATED_INTERNET_ACCESS, configuration.validatedInternetAccess); XmlUtil.writeNextValue( out, XML_TAG_NO_INTERNET_ACCESS_EXPECTED, configuration.noInternetAccessExpected); XmlUtil.writeNextValue(out, XML_TAG_METERED_HINT, configuration.meteredHint); XmlUtil.writeNextValue(out, XML_TAG_METERED_OVERRIDE, configuration.meteredOverride); XmlUtil.writeNextValue( out, XML_TAG_USE_EXTERNAL_SCORES, configuration.useExternalScores); XmlUtil.writeNextValue(out, XML_TAG_CREATOR_UID, configuration.creatorUid); XmlUtil.writeNextValue(out, XML_TAG_CREATOR_NAME, configuration.creatorName); XmlUtil.writeNextValue(out, XML_TAG_LAST_UPDATE_UID, configuration.lastUpdateUid); XmlUtil.writeNextValue(out, XML_TAG_LAST_UPDATE_NAME, configuration.lastUpdateName); XmlUtil.writeNextValue(out, XML_TAG_LAST_CONNECT_UID, configuration.lastConnectUid); XmlUtil.writeNextValue( out, XML_TAG_IS_LEGACY_PASSPOINT_CONFIG, configuration.isLegacyPasspointConfig); XmlUtil.writeNextValue( out, XML_TAG_ROAMING_CONSORTIUM_OIS, configuration.roamingConsortiumIds); XmlUtil.writeNextValue(out, XML_TAG_RANDOMIZED_MAC_ADDRESS, configuration.getRandomizedMacAddress().toString()); XmlUtil.writeNextValue(out, XML_TAG_MAC_RANDOMIZATION_SETTING, configuration.macRandomizationSetting); XmlUtil.writeNextValue(out, XML_TAG_CARRIER_ID, configuration.carrierId); XmlUtil.writeNextValue(out, XML_TAG_IS_MOST_RECENTLY_CONNECTED, configuration.isMostRecentlyConnected); XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_ID, configuration.subscriptionId); } /** * Populate wepKeys array elements only if they were non-empty in the backup data. * * @throws XmlPullParserException if parsing errors occur. */ private static void populateWepKeysFromXmlValue(Object value, String[] wepKeys) throws XmlPullParserException, IOException { String[] wepKeysInData = (String[]) value; if (wepKeysInData == null) { return; } if (wepKeysInData.length != wepKeys.length) { throw new XmlPullParserException( "Invalid Wep Keys length: " + wepKeysInData.length); } for (int i = 0; i < wepKeys.length; i++) { if (wepKeysInData[i].isEmpty()) { wepKeys[i] = null; } else { wepKeys[i] = wepKeysInData[i]; } } } private static SecurityParams parseSecurityParamsFromXml( XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException { SecurityParams params = null; while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); String tagName = valueName[0]; if (tagName == null) { throw new XmlPullParserException("Missing value name"); } switch (tagName) { case WifiConfigurationXmlUtil.XML_TAG_SECURITY_TYPE: params = SecurityParams.createSecurityParamsBySecurityType((int) value); break; case WifiConfigurationXmlUtil.XML_TAG_SAE_IS_H2E_ONLY_MODE: if (null == params) { throw new XmlPullParserException("Missing security type."); } params.enableSaeH2eOnlyMode((boolean) value); break; case WifiConfigurationXmlUtil.XML_TAG_SAE_IS_PK_ONLY_MODE: if (null == params) { throw new XmlPullParserException("Missing security type."); } params.enableSaePkOnlyMode((boolean) value); break; case WifiConfigurationXmlUtil.XML_TAG_IS_ADDED_BY_AUTO_UPGRADE: if (null == params) { throw new XmlPullParserException("Missing security type."); } params.setIsAddedByAutoUpgrade((boolean) value); break; } } return params; } private static void parseSecurityParamsListFromXml( XmlPullParser in, int outerTagDepth, WifiConfiguration configuration) throws XmlPullParserException, IOException { List paramsList = new ArrayList<>(); while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { switch (in.getName()) { case WifiConfigurationXmlUtil.XML_TAG_SECURITY_PARAMS: SecurityParams params = parseSecurityParamsFromXml(in, outerTagDepth + 1); if (params != null) { paramsList.add(params); } break; } } if (!paramsList.isEmpty()) { configuration.setSecurityParams(paramsList); } } /** * Parses the configuration data elements from the provided XML stream to a * WifiConfiguration object. * Note: This is used for parsing both backup data and config store data. Looping through * the tags make it easy to add or remove elements in the future versions if needed. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. * @param shouldExpectEncryptedCredentials Whether to expect encrypted credentials or not. * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. * @param fromSuggestion Is this WifiConfiguration created from a WifiNetworkSuggestion. * @return Pair if parsing is successful, * null otherwise. */ public static Pair parseFromXml( XmlPullParser in, int outerTagDepth, boolean shouldExpectEncryptedCredentials, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil, boolean fromSuggestion) throws XmlPullParserException, IOException { WifiConfiguration configuration = new WifiConfiguration(); String configKeyInData = null; boolean macRandomizationSettingExists = false; // Loop through and parse out all the elements from the stream within this section. while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { if (in.getAttributeValue(null, "name") != null) { // Value elements. String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); if (valueName[0] == null) { throw new XmlPullParserException("Missing value name"); } switch (valueName[0]) { case XML_TAG_CONFIG_KEY: configKeyInData = (String) value; break; case XML_TAG_SSID: configuration.SSID = (String) value; break; case XML_TAG_BSSID: configuration.BSSID = (String) value; break; case XML_TAG_PRE_SHARED_KEY: configuration.preSharedKey = (String) value; break; case XML_TAG_WEP_KEYS: populateWepKeysFromXmlValue(value, configuration.wepKeys); break; case XML_TAG_WEP_TX_KEY_INDEX: configuration.wepTxKeyIndex = (int) value; break; case XML_TAG_HIDDEN_SSID: configuration.hiddenSSID = (boolean) value; break; case XML_TAG_REQUIRE_PMF: configuration.requirePmf = (boolean) value; break; case XML_TAG_ALLOWED_KEY_MGMT: byte[] allowedKeyMgmt = (byte[]) value; configuration.allowedKeyManagement = BitSet.valueOf(allowedKeyMgmt); break; case XML_TAG_ALLOWED_PROTOCOLS: byte[] allowedProtocols = (byte[]) value; configuration.allowedProtocols = BitSet.valueOf(allowedProtocols); break; case XML_TAG_ALLOWED_AUTH_ALGOS: byte[] allowedAuthAlgorithms = (byte[]) value; configuration.allowedAuthAlgorithms = BitSet.valueOf( allowedAuthAlgorithms); break; case XML_TAG_ALLOWED_GROUP_CIPHERS: byte[] allowedGroupCiphers = (byte[]) value; configuration.allowedGroupCiphers = BitSet.valueOf(allowedGroupCiphers); break; case XML_TAG_ALLOWED_PAIRWISE_CIPHERS: byte[] allowedPairwiseCiphers = (byte[]) value; configuration.allowedPairwiseCiphers = BitSet.valueOf(allowedPairwiseCiphers); break; case XML_TAG_ALLOWED_GROUP_MGMT_CIPHERS: byte[] allowedGroupMgmtCiphers = (byte[]) value; configuration.allowedGroupManagementCiphers = BitSet.valueOf(allowedGroupMgmtCiphers); break; case XML_TAG_ALLOWED_SUITE_B_CIPHERS: byte[] allowedSuiteBCiphers = (byte[]) value; configuration.allowedSuiteBCiphers = BitSet.valueOf(allowedSuiteBCiphers); break; case XML_TAG_SHARED: configuration.shared = (boolean) value; break; case XML_TAG_STATUS: int status = (int) value; // Any network which was CURRENT before reboot needs // to be restored to ENABLED. if (status == WifiConfiguration.Status.CURRENT) { status = WifiConfiguration.Status.ENABLED; } configuration.status = status; break; case XML_TAG_FQDN: configuration.FQDN = (String) value; break; case XML_TAG_PROVIDER_FRIENDLY_NAME: configuration.providerFriendlyName = (String) value; break; case XML_TAG_LINKED_NETWORKS_LIST: configuration.linkedConfigurations = (HashMap) value; break; case XML_TAG_DEFAULT_GW_MAC_ADDRESS: configuration.defaultGwMacAddress = (String) value; break; case XML_TAG_VALIDATED_INTERNET_ACCESS: configuration.validatedInternetAccess = (boolean) value; break; case XML_TAG_NO_INTERNET_ACCESS_EXPECTED: configuration.noInternetAccessExpected = (boolean) value; break; case XML_TAG_METERED_HINT: configuration.meteredHint = (boolean) value; break; case XML_TAG_METERED_OVERRIDE: configuration.meteredOverride = (int) value; break; case XML_TAG_USE_EXTERNAL_SCORES: configuration.useExternalScores = (boolean) value; break; case XML_TAG_CREATOR_UID: configuration.creatorUid = (int) value; break; case XML_TAG_CREATOR_NAME: configuration.creatorName = (String) value; break; case XML_TAG_LAST_UPDATE_UID: configuration.lastUpdateUid = (int) value; break; case XML_TAG_LAST_UPDATE_NAME: configuration.lastUpdateName = (String) value; break; case XML_TAG_LAST_CONNECT_UID: configuration.lastConnectUid = (int) value; break; case XML_TAG_IS_LEGACY_PASSPOINT_CONFIG: configuration.isLegacyPasspointConfig = (boolean) value; break; case XML_TAG_ROAMING_CONSORTIUM_OIS: configuration.roamingConsortiumIds = (long[]) value; break; case XML_TAG_RANDOMIZED_MAC_ADDRESS: configuration.setRandomizedMacAddress( MacAddress.fromString((String) value)); break; case XML_TAG_MAC_RANDOMIZATION_SETTING: configuration.macRandomizationSetting = (int) value; macRandomizationSettingExists = true; break; case XML_TAG_CARRIER_ID: configuration.carrierId = (int) value; break; case XML_TAG_SUBSCRIPTION_ID: configuration.subscriptionId = (int) value; break; case XML_TAG_IS_AUTO_JOIN: configuration.allowAutojoin = (boolean) value; break; case XML_TAG_DELETION_PRIORITY: configuration.setDeletionPriority((int) value); break; case XML_TAG_NUM_REBOOTS_SINCE_LAST_USE: configuration.numRebootsSinceLastUse = (int) value; break; case XML_TAG_IS_TRUSTED: configuration.trusted = (boolean) value; break; case XML_TAG_IS_OEM_PAID: configuration.oemPaid = (boolean) value; break; case XML_TAG_IS_OEM_PRIVATE: configuration.oemPrivate = (boolean) value; break; case XML_TAG_IS_MOST_RECENTLY_CONNECTED: configuration.isMostRecentlyConnected = (boolean) value; break; case XML_TAG_IS_CARRIER_MERGED: configuration.carrierMerged = (boolean) value; default: Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]); break; } } else { String tagName = in.getName(); if (tagName == null) { throw new XmlPullParserException("Unexpected null tag found"); } switch (tagName) { case XML_TAG_PRE_SHARED_KEY: if (!shouldExpectEncryptedCredentials || encryptionUtil == null) { throw new XmlPullParserException( "Encrypted preSharedKey section not expected"); } EncryptedData encryptedData = EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1); byte[] preSharedKeyBytes = encryptionUtil.decrypt(encryptedData); if (preSharedKeyBytes == null) { Log.wtf(TAG, "Decryption of preSharedKey failed"); } else { configuration.preSharedKey = new String(preSharedKeyBytes); } break; case XML_TAG_SECURITY_PARAMS_LIST: parseSecurityParamsListFromXml(in, outerTagDepth + 1, configuration); break; default: Log.w(TAG, "Ignoring unknown tag found: " + tagName); break; } } } if (!macRandomizationSettingExists) { configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE; } if (configuration.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_PERSISTENT && !fromSuggestion) { configuration.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_AUTO; } configuration.convertLegacyFieldsToSecurityParamsIfNeeded(); return Pair.create(configKeyInData, configuration); } } /** * Utility class to serialize and deseriaize {@link IpConfiguration} object to XML & vice versa. * This is used by both {@link com.android.server.wifi.WifiConfigStore} & * {@link com.android.server.wifi.WifiBackupRestore} modules. */ public static class IpConfigurationXmlUtil { /** * List of XML tags corresponding to IpConfiguration object elements. */ public static final String XML_TAG_IP_ASSIGNMENT = "IpAssignment"; public static final String XML_TAG_LINK_ADDRESS = "LinkAddress"; public static final String XML_TAG_LINK_PREFIX_LENGTH = "LinkPrefixLength"; public static final String XML_TAG_GATEWAY_ADDRESS = "GatewayAddress"; public static final String XML_TAG_DNS_SERVER_ADDRESSES = "DNSServers"; public static final String XML_TAG_PROXY_SETTINGS = "ProxySettings"; public static final String XML_TAG_PROXY_HOST = "ProxyHost"; public static final String XML_TAG_PROXY_PORT = "ProxyPort"; public static final String XML_TAG_PROXY_PAC_FILE = "ProxyPac"; public static final String XML_TAG_PROXY_EXCLUSION_LIST = "ProxyExclusionList"; private static List parseProxyExclusionListString( @Nullable String exclusionListString) { if (exclusionListString == null) { return Collections.emptyList(); } else { return Arrays.asList(exclusionListString.toLowerCase(Locale.ROOT).split(",")); } } private static String generateProxyExclusionListString(@NonNull String[] exclusionList) { return TextUtils.join(",", exclusionList); } /** * Write the static IP configuration data elements to XML stream. */ private static void writeStaticIpConfigurationToXml( XmlSerializer out, StaticIpConfiguration staticIpConfiguration) throws XmlPullParserException, IOException { if (staticIpConfiguration.getIpAddress() != null) { XmlUtil.writeNextValue( out, XML_TAG_LINK_ADDRESS, staticIpConfiguration.getIpAddress().getAddress().getHostAddress()); XmlUtil.writeNextValue( out, XML_TAG_LINK_PREFIX_LENGTH, staticIpConfiguration.getIpAddress().getPrefixLength()); } else { XmlUtil.writeNextValue( out, XML_TAG_LINK_ADDRESS, null); XmlUtil.writeNextValue( out, XML_TAG_LINK_PREFIX_LENGTH, null); } if (staticIpConfiguration.getGateway() != null) { XmlUtil.writeNextValue( out, XML_TAG_GATEWAY_ADDRESS, staticIpConfiguration.getGateway().getHostAddress()); } else { XmlUtil.writeNextValue( out, XML_TAG_GATEWAY_ADDRESS, null); } // Create a string array of DNS server addresses String[] dnsServers = new String[staticIpConfiguration.getDnsServers().size()]; int dnsServerIdx = 0; for (InetAddress inetAddr : staticIpConfiguration.getDnsServers()) { dnsServers[dnsServerIdx++] = inetAddr.getHostAddress(); } XmlUtil.writeNextValue( out, XML_TAG_DNS_SERVER_ADDRESSES, dnsServers); } /** * Write the IP configuration data elements from the provided Configuration to the XML * stream. * * @param out XmlSerializer instance pointing to the XML stream. * @param ipConfiguration IpConfiguration object to be serialized. */ public static void writeToXml(XmlSerializer out, IpConfiguration ipConfiguration) throws XmlPullParserException, IOException { // Write IP assignment settings XmlUtil.writeNextValue(out, XML_TAG_IP_ASSIGNMENT, ipConfiguration.getIpAssignment().toString()); switch (ipConfiguration.getIpAssignment()) { case STATIC: writeStaticIpConfigurationToXml( out, ipConfiguration.getStaticIpConfiguration()); break; case DHCP: case UNASSIGNED: break; default: Log.w(TAG, "Ignoring unknown ip assignment type: " + ipConfiguration.getIpAssignment()); break; } // Write proxy settings XmlUtil.writeNextValue( out, XML_TAG_PROXY_SETTINGS, ipConfiguration.getProxySettings().toString()); switch (ipConfiguration.getProxySettings()) { case STATIC: XmlUtil.writeNextValue( out, XML_TAG_PROXY_HOST, ipConfiguration.getHttpProxy().getHost()); XmlUtil.writeNextValue( out, XML_TAG_PROXY_PORT, ipConfiguration.getHttpProxy().getPort()); XmlUtil.writeNextValue( out, XML_TAG_PROXY_EXCLUSION_LIST, generateProxyExclusionListString( ipConfiguration.getHttpProxy().getExclusionList())); break; case PAC: XmlUtil.writeNextValue( out, XML_TAG_PROXY_PAC_FILE, ipConfiguration.getHttpProxy().getPacFileUrl().toString()); break; case NONE: case UNASSIGNED: break; default: Log.w(TAG, "Ignoring unknown proxy settings type: " + ipConfiguration.getProxySettings()); break; } } /** * Parse out the static IP configuration from the XML stream. */ private static StaticIpConfiguration parseStaticIpConfigurationFromXml(XmlPullParser in) throws XmlPullParserException, IOException { StaticIpConfiguration.Builder builder = new StaticIpConfiguration.Builder(); String linkAddressString = (String) XmlUtil.readNextValueWithName(in, XML_TAG_LINK_ADDRESS); Integer linkPrefixLength = (Integer) XmlUtil.readNextValueWithName(in, XML_TAG_LINK_PREFIX_LENGTH); if (linkAddressString != null && linkPrefixLength != null) { LinkAddress linkAddress = new LinkAddress( InetAddresses.parseNumericAddress(linkAddressString), linkPrefixLength); if (linkAddress.getAddress() instanceof Inet4Address) { builder.setIpAddress(linkAddress); } else { Log.w(TAG, "Non-IPv4 address: " + linkAddress); } } String gatewayAddressString = (String) XmlUtil.readNextValueWithName(in, XML_TAG_GATEWAY_ADDRESS); if (gatewayAddressString != null) { InetAddress gateway = InetAddresses.parseNumericAddress(gatewayAddressString); RouteInfo route = new RouteInfo(null, gateway, null, RouteInfo.RTN_UNICAST); if (route.isDefaultRoute() && route.getDestination().getAddress() instanceof Inet4Address) { builder.setGateway(gateway); } else { Log.w(TAG, "Non-IPv4 default route: " + route); } } String[] dnsServerAddressesString = (String[]) XmlUtil.readNextValueWithName(in, XML_TAG_DNS_SERVER_ADDRESSES); if (dnsServerAddressesString != null) { List dnsServerAddresses = new ArrayList<>(); for (String dnsServerAddressString : dnsServerAddressesString) { InetAddress dnsServerAddress = InetAddresses.parseNumericAddress(dnsServerAddressString); dnsServerAddresses.add(dnsServerAddress); } builder.setDnsServers(dnsServerAddresses); } return builder.build(); } /** * Parses the IP configuration data elements from the provided XML stream to an * IpConfiguration object. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. * @return IpConfiguration object if parsing is successful, null otherwise. */ public static IpConfiguration parseFromXml(XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException { IpConfiguration ipConfiguration = new IpConfiguration(); // Parse out the IP assignment info first. String ipAssignmentString = (String) XmlUtil.readNextValueWithName(in, XML_TAG_IP_ASSIGNMENT); IpAssignment ipAssignment = IpAssignment.valueOf(ipAssignmentString); ipConfiguration.setIpAssignment(ipAssignment); switch (ipAssignment) { case STATIC: ipConfiguration.setStaticIpConfiguration(parseStaticIpConfigurationFromXml(in)); break; case DHCP: case UNASSIGNED: break; default: Log.w(TAG, "Ignoring unknown ip assignment type: " + ipAssignment); break; } // Parse out the proxy settings next. String proxySettingsString = (String) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_SETTINGS); ProxySettings proxySettings = ProxySettings.valueOf(proxySettingsString); ipConfiguration.setProxySettings(proxySettings); switch (proxySettings) { case STATIC: String proxyHost = (String) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_HOST); int proxyPort = (int) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_PORT); String proxyExclusionList = (String) XmlUtil.readNextValueWithName( in, XML_TAG_PROXY_EXCLUSION_LIST); ipConfiguration.setHttpProxy( ProxyInfo.buildDirectProxy( proxyHost, proxyPort, parseProxyExclusionListString(proxyExclusionList))); break; case PAC: String proxyPacFile = (String) XmlUtil.readNextValueWithName(in, XML_TAG_PROXY_PAC_FILE); ipConfiguration.setHttpProxy( ProxyInfo.buildPacProxy(Uri.parse(proxyPacFile))); break; case NONE: case UNASSIGNED: break; default: Log.w(TAG, "Ignoring unknown proxy settings type: " + proxySettings); break; } return ipConfiguration; } } /** * Utility class to serialize and deserialize {@link NetworkSelectionStatus} object to XML & * vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module. */ public static class NetworkSelectionStatusXmlUtil { /** * List of XML tags corresponding to NetworkSelectionStatus object elements. */ public static final String XML_TAG_SELECTION_STATUS = "SelectionStatus"; public static final String XML_TAG_DISABLE_REASON = "DisableReason"; public static final String XML_TAG_CONNECT_CHOICE = "ConnectChoice"; public static final String XML_TAG_HAS_EVER_CONNECTED = "HasEverConnected"; public static final String XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED = "CaptivePortalNeverDetected"; public static final String XML_TAG_CONNECT_CHOICE_RSSI = "ConnectChoiceRssi"; /** * Write the NetworkSelectionStatus data elements from the provided status to the XML * stream. * * @param out XmlSerializer instance pointing to the XML stream. * @param selectionStatus NetworkSelectionStatus object to be serialized. */ public static void writeToXml(XmlSerializer out, NetworkSelectionStatus selectionStatus) throws XmlPullParserException, IOException { XmlUtil.writeNextValue( out, XML_TAG_SELECTION_STATUS, selectionStatus.getNetworkStatusString()); XmlUtil.writeNextValue( out, XML_TAG_DISABLE_REASON, selectionStatus.getNetworkSelectionDisableReasonString()); XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE, selectionStatus.getConnectChoice()); XmlUtil.writeNextValue(out, XML_TAG_CONNECT_CHOICE_RSSI, selectionStatus.getConnectChoiceRssi()); XmlUtil.writeNextValue( out, XML_TAG_HAS_EVER_CONNECTED, selectionStatus.hasEverConnected()); XmlUtil.writeNextValue(out, XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED, selectionStatus.hasNeverDetectedCaptivePortal()); } /** * Parses the NetworkSelectionStatus data elements from the provided XML stream to a * NetworkSelectionStatus object. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. * @return NetworkSelectionStatus object if parsing is successful, null otherwise. */ public static NetworkSelectionStatus parseFromXml(XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException { NetworkSelectionStatus selectionStatus = new NetworkSelectionStatus(); String statusString = ""; String disableReasonString = ""; // Initialize hasNeverDetectedCaptivePortal to "false" for upgrading legacy configs // which do not have the XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED tag. selectionStatus.setHasNeverDetectedCaptivePortal(false); // Loop through and parse out all the elements from the stream within this section. while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); if (valueName[0] == null) { throw new XmlPullParserException("Missing value name"); } switch (valueName[0]) { case XML_TAG_SELECTION_STATUS: statusString = (String) value; break; case XML_TAG_DISABLE_REASON: disableReasonString = (String) value; break; case XML_TAG_CONNECT_CHOICE: selectionStatus.setConnectChoice((String) value); break; case XML_TAG_CONNECT_CHOICE_RSSI: selectionStatus.setConnectChoiceRssi((int) value); break; case XML_TAG_HAS_EVER_CONNECTED: selectionStatus.setHasEverConnected((boolean) value); break; case XML_TAG_IS_CAPTIVE_PORTAL_NEVER_DETECTED: selectionStatus.setHasNeverDetectedCaptivePortal((boolean) value); default: Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]); break; } } // Now figure out the network selection status codes from |selectionStatusString| & // |disableReasonString|. int status = Arrays.asList(NetworkSelectionStatus.QUALITY_NETWORK_SELECTION_STATUS) .indexOf(statusString); int disableReason = NetworkSelectionStatus.getDisableReasonByString(disableReasonString); // If either of the above codes are invalid or if the network was temporarily disabled // (blacklisted), restore the status as enabled. We don't want to persist blacklists // across reboots. if (status == -1 || disableReason == -1 || status == NetworkSelectionStatus.NETWORK_SELECTION_TEMPORARY_DISABLED) { status = NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; disableReason = NetworkSelectionStatus.DISABLED_NONE; } selectionStatus.setNetworkSelectionStatus(status); selectionStatus.setNetworkSelectionDisableReason(disableReason); if (status == NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED) { // Make the counter non-zero so that logging code works properly selectionStatus.setDisableReasonCounter(disableReason, 1); } return selectionStatus; } } /** * Utility class to serialize and deseriaize {@link WifiEnterpriseConfig} object to XML & * vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module. */ public static class WifiEnterpriseConfigXmlUtil { /** * List of XML tags corresponding to WifiEnterpriseConfig object elements. */ public static final String XML_TAG_IDENTITY = "Identity"; public static final String XML_TAG_ANON_IDENTITY = "AnonIdentity"; public static final String XML_TAG_PASSWORD = "Password"; public static final String XML_TAG_CLIENT_CERT = "ClientCert"; public static final String XML_TAG_CA_CERT = "CaCert"; public static final String XML_TAG_SUBJECT_MATCH = "SubjectMatch"; public static final String XML_TAG_ENGINE = "Engine"; public static final String XML_TAG_ENGINE_ID = "EngineId"; public static final String XML_TAG_PRIVATE_KEY_ID = "PrivateKeyId"; public static final String XML_TAG_ALT_SUBJECT_MATCH = "AltSubjectMatch"; public static final String XML_TAG_DOM_SUFFIX_MATCH = "DomSuffixMatch"; public static final String XML_TAG_CA_PATH = "CaPath"; public static final String XML_TAG_EAP_METHOD = "EapMethod"; public static final String XML_TAG_PHASE2_METHOD = "Phase2Method"; public static final String XML_TAG_PLMN = "PLMN"; public static final String XML_TAG_REALM = "Realm"; public static final String XML_TAG_OCSP = "Ocsp"; public static final String XML_TAG_WAPI_CERT_SUITE = "WapiCertSuite"; public static final String XML_TAG_APP_INSTALLED_ROOT_CA_CERT = "AppInstalledRootCaCert"; public static final String XML_TAG_APP_INSTALLED_PRIVATE_KEY = "AppInstalledPrivateKey"; public static final String XML_TAG_KEYCHAIN_KEY_ALIAS = "KeyChainAlias"; public static final String XML_TAG_DECORATED_IDENTITY_PREFIX = "DecoratedIdentityPrefix"; /** * Write password key to the XML stream. * * If encryptionUtil is null or if encryption fails for some reason, the password is stored * in plaintext, else the encrypted psk is stored. */ private static void writePasswordToXml( XmlSerializer out, String password, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { EncryptedData encryptedData = null; if (encryptionUtil != null) { if (password != null) { encryptedData = encryptionUtil.encrypt(password.getBytes()); if (encryptedData == null) { // We silently fail encryption failures! Log.wtf(TAG, "Encryption of password failed"); } } } if (encryptedData != null) { XmlUtil.writeNextSectionStart(out, XML_TAG_PASSWORD); EncryptedDataXmlUtil.writeToXml(out, encryptedData); XmlUtil.writeNextSectionEnd(out, XML_TAG_PASSWORD); } else { XmlUtil.writeNextValue(out, XML_TAG_PASSWORD, password); } } /** * Write the WifiEnterpriseConfig data elements from the provided config to the XML * stream. * * @param out XmlSerializer instance pointing to the XML stream. * @param enterpriseConfig WifiEnterpriseConfig object to be serialized. * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. */ public static void writeToXml(XmlSerializer out, WifiEnterpriseConfig enterpriseConfig, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { XmlUtil.writeNextValue(out, XML_TAG_IDENTITY, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.IDENTITY_KEY)); XmlUtil.writeNextValue(out, XML_TAG_ANON_IDENTITY, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ANON_IDENTITY_KEY)); writePasswordToXml( out, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PASSWORD_KEY), encryptionUtil); XmlUtil.writeNextValue(out, XML_TAG_CLIENT_CERT, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CLIENT_CERT_KEY)); XmlUtil.writeNextValue(out, XML_TAG_CA_CERT, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY)); XmlUtil.writeNextValue(out, XML_TAG_SUBJECT_MATCH, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.SUBJECT_MATCH_KEY)); XmlUtil.writeNextValue(out, XML_TAG_ENGINE, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_KEY)); XmlUtil.writeNextValue(out, XML_TAG_ENGINE_ID, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ENGINE_ID_KEY)); XmlUtil.writeNextValue(out, XML_TAG_PRIVATE_KEY_ID, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY)); XmlUtil.writeNextValue(out, XML_TAG_ALT_SUBJECT_MATCH, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY)); XmlUtil.writeNextValue(out, XML_TAG_DOM_SUFFIX_MATCH, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY)); XmlUtil.writeNextValue(out, XML_TAG_CA_PATH, enterpriseConfig.getFieldValue(WifiEnterpriseConfig.CA_PATH_KEY)); XmlUtil.writeNextValue(out, XML_TAG_EAP_METHOD, enterpriseConfig.getEapMethod()); XmlUtil.writeNextValue(out, XML_TAG_PHASE2_METHOD, enterpriseConfig.getPhase2Method()); XmlUtil.writeNextValue(out, XML_TAG_PLMN, enterpriseConfig.getPlmn()); XmlUtil.writeNextValue(out, XML_TAG_REALM, enterpriseConfig.getRealm()); XmlUtil.writeNextValue(out, XML_TAG_OCSP, enterpriseConfig.getOcsp()); XmlUtil.writeNextValue(out, XML_TAG_WAPI_CERT_SUITE, enterpriseConfig.getWapiCertSuite()); XmlUtil.writeNextValue(out, XML_TAG_APP_INSTALLED_ROOT_CA_CERT, enterpriseConfig.isAppInstalledCaCert()); XmlUtil.writeNextValue(out, XML_TAG_APP_INSTALLED_PRIVATE_KEY, enterpriseConfig.isAppInstalledDeviceKeyAndCert()); XmlUtil.writeNextValue(out, XML_TAG_KEYCHAIN_KEY_ALIAS, enterpriseConfig.getClientKeyPairAliasInternal()); if (SdkLevel.isAtLeastS()) { XmlUtil.writeNextValue(out, XML_TAG_DECORATED_IDENTITY_PREFIX, enterpriseConfig.getDecoratedIdentityPrefix()); } } /** * Parses the data elements from the provided XML stream to a WifiEnterpriseConfig object. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. * @param shouldExpectEncryptedCredentials Whether to expect encrypted credentials or not. * @param encryptionUtil Instance of {@link EncryptedDataXmlUtil}. * @return WifiEnterpriseConfig object if parsing is successful, null otherwise. */ public static WifiEnterpriseConfig parseFromXml(XmlPullParser in, int outerTagDepth, boolean shouldExpectEncryptedCredentials, @Nullable WifiConfigStoreEncryptionUtil encryptionUtil) throws XmlPullParserException, IOException { WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig(); // Loop through and parse out all the elements from the stream within this section. while (XmlUtilHelper.nextElementWithin(in, outerTagDepth)) { if (in.getAttributeValue(null, "name") != null) { // Value elements. String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); if (valueName[0] == null) { throw new XmlPullParserException("Missing value name"); } switch (valueName[0]) { case XML_TAG_IDENTITY: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.IDENTITY_KEY, (String) value); break; case XML_TAG_ANON_IDENTITY: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.ANON_IDENTITY_KEY, (String) value); break; case XML_TAG_PASSWORD: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.PASSWORD_KEY, (String) value); if (shouldExpectEncryptedCredentials && !TextUtils.isEmpty(enterpriseConfig.getFieldValue( WifiEnterpriseConfig.PASSWORD_KEY))) { // Indicates that encryption of password failed when it was last // written. Log.e(TAG, "password value not expected"); } break; case XML_TAG_CLIENT_CERT: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.CLIENT_CERT_KEY, (String) value); break; case XML_TAG_CA_CERT: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.CA_CERT_KEY, (String) value); break; case XML_TAG_SUBJECT_MATCH: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.SUBJECT_MATCH_KEY, (String) value); break; case XML_TAG_ENGINE: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.ENGINE_KEY, (String) value); break; case XML_TAG_ENGINE_ID: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.ENGINE_ID_KEY, (String) value); break; case XML_TAG_PRIVATE_KEY_ID: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY, (String) value); break; case XML_TAG_ALT_SUBJECT_MATCH: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY, (String) value); break; case XML_TAG_DOM_SUFFIX_MATCH: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY, (String) value); break; case XML_TAG_CA_PATH: enterpriseConfig.setFieldValue( WifiEnterpriseConfig.CA_PATH_KEY, (String) value); break; case XML_TAG_OCSP: enterpriseConfig.setOcsp((int) value); break; case XML_TAG_EAP_METHOD: enterpriseConfig.setEapMethod((int) value); break; case XML_TAG_PHASE2_METHOD: enterpriseConfig.setPhase2Method((int) value); break; case XML_TAG_PLMN: enterpriseConfig.setPlmn((String) value); break; case XML_TAG_REALM: enterpriseConfig.setRealm((String) value); break; case XML_TAG_WAPI_CERT_SUITE: enterpriseConfig.setWapiCertSuite((String) value); break; case XML_TAG_APP_INSTALLED_ROOT_CA_CERT: enterpriseConfig.initIsAppInstalledCaCert((boolean) value); break; case XML_TAG_APP_INSTALLED_PRIVATE_KEY: enterpriseConfig.initIsAppInstalledDeviceKeyAndCert((boolean) value); break; case XML_TAG_KEYCHAIN_KEY_ALIAS: if (SdkLevel.isAtLeastS()) { enterpriseConfig.setClientKeyPairAlias((String) value); } break; case XML_TAG_DECORATED_IDENTITY_PREFIX: if (SdkLevel.isAtLeastS()) { enterpriseConfig.setDecoratedIdentityPrefix((String) value); } break; default: Log.w(TAG, "Ignoring unknown value name found: " + valueName[0]); break; } } else { String tagName = in.getName(); if (tagName == null) { throw new XmlPullParserException("Unexpected null tag found"); } switch (tagName) { case XML_TAG_PASSWORD: if (!shouldExpectEncryptedCredentials || encryptionUtil == null) { throw new XmlPullParserException( "encrypted password section not expected"); } EncryptedData encryptedData = EncryptedDataXmlUtil.parseFromXml(in, outerTagDepth + 1); byte[] passwordBytes = encryptionUtil.decrypt(encryptedData); if (passwordBytes == null) { Log.wtf(TAG, "Decryption of password failed"); } else { enterpriseConfig.setFieldValue( WifiEnterpriseConfig.PASSWORD_KEY, new String(passwordBytes)); } break; default: Log.w(TAG, "Ignoring unknown tag name found: " + tagName); break; } } } return enterpriseConfig; } } /** * Utility class to serialize and deseriaize {@link EncryptedData} object to XML & * vice versa. This is used by {@link com.android.server.wifi.WifiConfigStore} module. */ public static class EncryptedDataXmlUtil { /** * List of XML tags corresponding to EncryptedData object elements. */ private static final String XML_TAG_ENCRYPTED_DATA = "EncryptedData"; private static final String XML_TAG_IV = "IV"; /** * Write the NetworkSelectionStatus data elements from the provided status to the XML * stream. * * @param out XmlSerializer instance pointing to the XML stream. * @param encryptedData EncryptedData object to be serialized. */ public static void writeToXml(XmlSerializer out, EncryptedData encryptedData) throws XmlPullParserException, IOException { XmlUtil.writeNextValue( out, XML_TAG_ENCRYPTED_DATA, encryptedData.getEncryptedData()); XmlUtil.writeNextValue(out, XML_TAG_IV, encryptedData.getIv()); } /** * Parses the EncryptedData data elements from the provided XML stream to a * EncryptedData object. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. * @return EncryptedData object if parsing is successful, null otherwise. */ public static EncryptedData parseFromXml(XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException { byte[] encryptedData = null; byte[] iv = null; // Loop through and parse out all the elements from the stream within this section. while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); if (valueName[0] == null) { throw new XmlPullParserException("Missing value name"); } switch (valueName[0]) { case XML_TAG_ENCRYPTED_DATA: encryptedData = (byte[]) value; break; case XML_TAG_IV: iv = (byte[]) value; break; default: Log.e(TAG, "Unknown value name found: " + valueName[0]); break; } } return new EncryptedData(encryptedData, iv); } } public static boolean nextElementWithin(XmlPullParser parser, int outerDepth) throws IOException, XmlPullParserException { return XmlUtilHelper.nextElementWithin(parser, outerDepth); } /** * Utility class to serialize and deseriaize {@link SoftApConfiguration} object to XML * & vice versa. This is used by both {@link com.android.server.wifi.SoftApStore} modules. */ public static class SoftApConfigurationXmlUtil { /** * List of XML tags corresponding to SoftApConfiguration object elements. */ public static final String XML_TAG_CLIENT_MACADDRESS = "ClientMacAddress"; public static final String XML_TAG_BAND_CHANNEL = "BandChannel"; private static final String XML_TAG_BAND = "Band"; private static final String XML_TAG_CHANNEL = "Channel"; /** * Parses the client list from the provided XML stream to a ArrayList object. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. * @return ArrayList object if parsing is successful, null otherwise. */ public static List parseClientListFromXml(XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException, IllegalArgumentException { List clientList = new ArrayList<>(); // Loop through and parse out all the elements from the stream within this section. while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); if (valueName[0] == null) { throw new XmlPullParserException("Missing value name"); } switch (valueName[0]) { case XML_TAG_CLIENT_MACADDRESS: MacAddress client = MacAddress.fromString((String) value); clientList.add(client); break; default: Log.e(TAG, "Unknown value name found: " + valueName[0]); break; } } return clientList; } /** * Write the SoftApConfiguration client control list data elements * from the provided list to the XML stream. * * @param out XmlSerializer instance pointing to the XML stream. * @param clientList Client list object to be serialized. */ public static void writeClientListToXml(XmlSerializer out, List clientList) throws XmlPullParserException, IOException { for (MacAddress mac: clientList) { XmlUtil.writeNextValue(out, XML_TAG_CLIENT_MACADDRESS, mac.toString()); } } /** * Parses the band and channel from the provided XML stream to a SparseIntArray object. * * @param in XmlPullParser instance pointing to the XML stream. * @param outerTagDepth depth of the outer tag in the XML document. * @return SparseIntArray object if parsing is successful, null otherwise. */ public static SparseIntArray parseChannelsFromXml(XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException, IllegalArgumentException { SparseIntArray channels = new SparseIntArray(); while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) { int band = ApConfigUtil.INVALID_VALUE_FOR_BAND_OR_CHANNEL; int channel = ApConfigUtil.INVALID_VALUE_FOR_BAND_OR_CHANNEL; switch (in.getName()) { case XML_TAG_BAND_CHANNEL: while (!XmlUtil.isNextSectionEnd(in, outerTagDepth + 1)) { String[] valueName = new String[1]; Object value = XmlUtil.readCurrentValue(in, valueName); if (valueName[0] == null) { throw new XmlPullParserException("Missing value name"); } switch (valueName[0]) { case XML_TAG_BAND: band = (int) value; break; case XML_TAG_CHANNEL: channel = (int) value; break; default: Log.e(TAG, "Unknown value name found: " + valueName[0]); break; } } channels.put(band, channel); break; } } return channels; } /** * Write the SoftApConfiguration channels data elements * from the provided SparseIntArray to the XML stream. * * @param out XmlSerializer instance pointing to the XML stream. * @param channels SparseIntArray, which includes bands and channels, to be serialized. */ public static void writeChannelsToXml(XmlSerializer out, SparseIntArray channels) throws XmlPullParserException, IOException { for (int i = 0; i < channels.size(); i++) { XmlUtil.writeNextSectionStart(out, XML_TAG_BAND_CHANNEL); XmlUtil.writeNextValue(out, XML_TAG_BAND, channels.keyAt(i)); XmlUtil.writeNextValue(out, XML_TAG_CHANNEL, channels.valueAt(i)); XmlUtil.writeNextSectionEnd(out, XML_TAG_BAND_CHANNEL); } } } }