From 0cafbe0c8e1bb5b9900908a44df232251c2042ea Mon Sep 17 00:00:00 2001 From: Rebecca Silberstein Date: Wed, 19 Jul 2017 13:26:33 -0700 Subject: WifiApConfigStore: add initial apconfig validation Add initial validation for values in the supplied SoftAp config. The initial checks will not fully validate the supplied settings, but they will do a high level sanity check. Initial items verified: 1 - ssid string set 2 - ssid string not empty 3 - ssid string does not exceed max length 4 - open networks do not have a password 5 - password protected networks do have a password 6 - if applicable, passwords within min/max range Future extensions would include additional parameters and validating the bytes in the ssid and password fields. Bug: 37280779 Test: frameworks/opt/net/wifi/tests/wifitests/runtests.sh Change-Id: I1a9338ce92c60c88ff3992d024a00ab1c6c4ec5e --- .../com/android/server/wifi/WifiApConfigStore.java | 119 +++++++++++++++ .../android/server/wifi/WifiApConfigStoreTest.java | 160 +++++++++++++++++++++ 2 files changed, 279 insertions(+) diff --git a/service/java/com/android/server/wifi/WifiApConfigStore.java b/service/java/com/android/server/wifi/WifiApConfigStore.java index 9c90bcf47..98a59329b 100644 --- a/service/java/com/android/server/wifi/WifiApConfigStore.java +++ b/service/java/com/android/server/wifi/WifiApConfigStore.java @@ -16,13 +16,16 @@ package com.android.server.wifi; +import android.annotation.NonNull; import android.content.Context; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; import android.os.Environment; +import android.text.TextUtils; import android.util.Log; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; @@ -31,6 +34,7 @@ import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Random; import java.util.UUID; @@ -50,6 +54,15 @@ public class WifiApConfigStore { private static final int RAND_SSID_INT_MIN = 1000; private static final int RAND_SSID_INT_MAX = 9999; + @VisibleForTesting + static final int SSID_MIN_LEN = 1; + @VisibleForTesting + static final int SSID_MAX_LEN = 32; + @VisibleForTesting + static final int PSK_MIN_LEN = 8; + @VisibleForTesting + static final int PSK_MAX_LEN = 63; + private WifiConfiguration mWifiApConfig = null; private ArrayList mAllowed2GChannel = null; @@ -224,4 +237,110 @@ public class WifiApConfigStore { config.preSharedKey = randomUUID.substring(0, 8) + randomUUID.substring(9, 13); return config; } + + /** + * Verify provided SSID for existence, length and conversion to bytes + * + * @param ssid String ssid name + * @return boolean indicating ssid met requirements + */ + private static boolean validateApConfigSsid(String ssid) { + if (TextUtils.isEmpty(ssid)) { + Log.d(TAG, "SSID for softap configuration must be set."); + return false; + } + + if (ssid.length() < SSID_MIN_LEN || ssid.length() > SSID_MAX_LEN) { + Log.d(TAG, "SSID for softap configuration string size must be at least " + + SSID_MIN_LEN + " and not more than " + SSID_MAX_LEN); + return false; + } + + try { + ssid.getBytes(StandardCharsets.UTF_8); + } catch (IllegalArgumentException e) { + Log.e(TAG, "softap config SSID verification failed: malformed string " + ssid); + return false; + } + return true; + } + + /** + * Verify provided preSharedKey in ap config for WPA2_PSK network meets requirements. + */ + private static boolean validateApConfigPreSharedKey(String preSharedKey) { + if (preSharedKey.length() < PSK_MIN_LEN || preSharedKey.length() > PSK_MAX_LEN) { + Log.d(TAG, "softap network password string size must be at least " + PSK_MIN_LEN + + " and no more than " + PSK_MAX_LEN); + return false; + } + + try { + preSharedKey.getBytes(StandardCharsets.UTF_8); + } catch (IllegalArgumentException e) { + Log.e(TAG, "softap network password verification failed: malformed string"); + return false; + } + return true; + } + + /** + * Validate a WifiConfiguration is properly configured for use by SoftApManager. + * + * This method checks the length of the SSID and for sanity between security settings (if it + * requires a password, was one provided?). + * + * @param apConfig {@link WifiConfiguration} to use for softap mode + * @return boolean true if the provided config meets the minimum set of details, false + * otherwise. + */ + static boolean validateApWifiConfiguration(@NonNull WifiConfiguration apConfig) { + // first check the SSID + if (!validateApConfigSsid(apConfig.SSID)) { + // failed SSID verificiation checks + return false; + } + + // now check security settings: settings app allows open and WPA2 PSK + if (apConfig.allowedKeyManagement == null) { + Log.d(TAG, "softap config key management bitset was null"); + return false; + } + + String preSharedKey = apConfig.preSharedKey; + boolean hasPreSharedKey = !TextUtils.isEmpty(preSharedKey); + int authType; + + try { + authType = apConfig.getAuthType(); + } catch (IllegalStateException e) { + Log.d(TAG, "Unable to get AuthType for softap config: " + e.getMessage()); + return false; + } + + if (authType == KeyMgmt.NONE) { + // open networks should not have a password + if (hasPreSharedKey) { + Log.d(TAG, "open softap network should not have a password"); + return false; + } + } else if (authType == KeyMgmt.WPA2_PSK) { + // this is a config that should have a password - check that first + if (!hasPreSharedKey) { + Log.d(TAG, "softap network password must be set"); + return false; + } + + if (!validateApConfigPreSharedKey(preSharedKey)) { + // failed preSharedKey checks + return false; + } + } else { + // this is not a supported security type + Log.d(TAG, "softap configs must either be open or WPA2 PSK networks"); + return false; + } + + return true; + } } diff --git a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java index 2d3b06695..02064d87e 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiApConfigStoreTest.java @@ -17,6 +17,7 @@ package com.android.server.wifi; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -36,6 +37,7 @@ import org.mockito.MockitoAnnotations; import java.io.File; import java.lang.reflect.Method; +import java.util.Random; /** * Unit tests for {@link com.android.server.wifi.WifiApConfigStore}. @@ -50,12 +52,15 @@ public class WifiApConfigStoreTest { private static final String TEST_DEFAULT_AP_SSID = "TestAP"; private static final String TEST_CONFIGURED_AP_SSID = "ConfiguredAP"; private static final String TEST_DEFAULT_HOTSPOT_SSID = "TestShare"; + private static final String TEST_DEFAULT_HOTSPOT_PSK = "TestPassword"; private static final int RAND_SSID_INT_MIN = 1000; private static final int RAND_SSID_INT_MAX = 9999; + private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789"; @Mock Context mContext; @Mock BackupManagerProxy mBackupManagerProxy; File mApConfigFile; + Random mRandom; @Before public void setUp() throws Exception { @@ -73,6 +78,8 @@ public class WifiApConfigStoreTest { resources.setString(R.string.wifi_localhotspot_configure_ssid_default, TEST_DEFAULT_HOTSPOT_SSID); when(mContext.getResources()).thenReturn(resources); + + mRandom = new Random(); } @After @@ -194,6 +201,17 @@ public class WifiApConfigStoreTest { verify(mBackupManagerProxy).notifyDataChanged(); } + /** + * Verify a proper WifiConfiguration is generate by getDefaultApConfiguration(). + */ + @Test + public void getDefaultApConfigurationIsValid() { + WifiApConfigStore store = new WifiApConfigStore( + mContext, mBackupManagerProxy, mApConfigFile.getPath()); + WifiConfiguration config = store.getApConfiguration(); + assertTrue(WifiApConfigStore.validateApWifiConfiguration(config)); + } + /** * Verify a proper local only hotspot config is generated when called properly with the valid * context. @@ -204,5 +222,147 @@ public class WifiApConfigStoreTest { verifyDefaultApConfig(config, TEST_DEFAULT_HOTSPOT_SSID); // The LOHS config should also have a specific network id set - check that as well. assertEquals(WifiConfiguration.LOCAL_ONLY_NETWORK_ID, config.networkId); + + // verify that the config passes the validateApWifiConfiguration check + assertTrue(WifiApConfigStore.validateApWifiConfiguration(config)); + } + + /** + * Helper method to generate random SSIDs. + * + * Note: this method has limited use as a random SSID generator. The characters used in this + * method do no not cover all valid inputs. + * @param length number of characters to generate for the name + * @return String generated string of random characters + */ + private String generateRandomString(int length) { + + StringBuilder stringBuilder = new StringBuilder(length); + int index = -1; + while (stringBuilder.length() < length) { + index = mRandom.nextInt(TEST_CHAR_SET_AS_STRING.length()); + stringBuilder.append(TEST_CHAR_SET_AS_STRING.charAt(index)); + } + return stringBuilder.toString(); + } + + /** + * Verify the SSID checks in validateApWifiConfiguration. + * + * Cases to check and verify they trigger failed verification: + * null WifiConfiguration.SSID + * empty WifiConfiguration.SSID + * invalid WifiConfiguaration.SSID length + * + * Additionally check a valid SSID with a random (within valid ranges) length. + */ + @Test + public void testSsidVerificationInValidateApWifiConfigurationCheck() { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = null; + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); + config.SSID = ""; + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); + // check a string that is too large + config.SSID = generateRandomString(WifiApConfigStore.SSID_MAX_LEN + 1); + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); + + // now check a valid SSID with a random length + config.SSID = generateRandomString(mRandom.nextInt(WifiApConfigStore.SSID_MAX_LEN + 1)); + assertTrue(WifiApConfigStore.validateApWifiConfiguration(config)); + } + + /** + * Verify the Open network checks in validateApWifiConfiguration. + * + * If the configured network is open, it should not have a password set. + * + * Additionally verify a valid open network passes verification. + */ + @Test + public void testOpenNetworkConfigInValidateApWifiConfigurationCheck() { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = TEST_DEFAULT_HOTSPOT_SSID; + + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + config.preSharedKey = TEST_DEFAULT_HOTSPOT_PSK; + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); + + // open networks should not have a password set + config.preSharedKey = null; + assertTrue(WifiApConfigStore.validateApWifiConfiguration(config)); + config.preSharedKey = ""; + assertTrue(WifiApConfigStore.validateApWifiConfiguration(config)); + } + + /** + * Verify the WPA2_PSK network checks in validateApWifiConfiguration. + * + * If the configured network is configured with a preSharedKey, verify that the passwork is set + * and it meets length requirements. + */ + @Test + public void testWpa2PskNetworkConfigInValidateApWifiConfigurationCheck() { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = TEST_DEFAULT_HOTSPOT_SSID; + + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK); + config.preSharedKey = null; + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); + config.preSharedKey = ""; + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); + + // test too short + config.preSharedKey = + generateRandomString(WifiApConfigStore.PSK_MIN_LEN - 1); + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); + + // test too long + config.preSharedKey = + generateRandomString(WifiApConfigStore.PSK_MAX_LEN + 1); + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); + + // explicitly test min length + config.preSharedKey = + generateRandomString(WifiApConfigStore.PSK_MIN_LEN); + assertTrue(WifiApConfigStore.validateApWifiConfiguration(config)); + + // explicitly test max length + config.preSharedKey = + generateRandomString(WifiApConfigStore.PSK_MAX_LEN); + assertTrue(WifiApConfigStore.validateApWifiConfiguration(config)); + + // test random (valid length) + int maxLen = WifiApConfigStore.PSK_MAX_LEN; + int minLen = WifiApConfigStore.PSK_MIN_LEN; + config.preSharedKey = + generateRandomString(mRandom.nextInt(maxLen - minLen) + minLen); + assertTrue(WifiApConfigStore.validateApWifiConfiguration(config)); + } + + /** + * Verify an invalid AuthType setting (that would trigger an IllegalStateException) + * returns false when triggered in the validateApWifiConfiguration. + */ + @Test + public void testInvalidAuthTypeInValidateApWifiConfigurationCheck() { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = TEST_DEFAULT_HOTSPOT_SSID; + + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA2_PSK); + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); + } + + /** + * Verify an unsupported authType returns false for validateApWifiConfigurationCheck. + */ + @Test + public void testUnsupportedAuthTypeInValidateApWifiConfigurationCheck() { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = TEST_DEFAULT_HOTSPOT_SSID; + + config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + assertFalse(WifiApConfigStore.validateApWifiConfiguration(config)); } } -- cgit v1.2.3