summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--service/java/com/android/server/wifi/WifiInjector.java4
-rw-r--r--service/java/com/android/server/wifi/WifiServiceImpl.java16
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointManager.java153
-rw-r--r--service/java/com/android/server/wifi/hotspot2/PasspointProvider.java35
-rw-r--r--tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java200
5 files changed, 395 insertions, 13 deletions
diff --git a/service/java/com/android/server/wifi/WifiInjector.java b/service/java/com/android/server/wifi/WifiInjector.java
index a5f132607..bfe6446af 100644
--- a/service/java/com/android/server/wifi/WifiInjector.java
+++ b/service/java/com/android/server/wifi/WifiInjector.java
@@ -91,6 +91,7 @@ public class WifiInjector {
private final WifiPermissionsWrapper mWifiPermissionsWrapper;
private final WifiPermissionsUtil mWifiPermissionsUtil;
private final PasspointManager mPasspointManager;
+ private final SIMAccessor mSimAccessor;
private final boolean mUseRealLogger;
@@ -166,7 +167,8 @@ public class WifiInjector {
mWifiPermissionsWrapper = new WifiPermissionsWrapper(mContext);
mWifiPermissionsUtil = new WifiPermissionsUtil(mWifiPermissionsWrapper, mContext,
mSettingsStore, UserManager.get(mContext));
- mPasspointManager = new PasspointManager(mContext, this);
+ mSimAccessor = new SIMAccessor(mContext);
+ mPasspointManager = new PasspointManager(mContext, this, mSimAccessor);
}
/**
diff --git a/service/java/com/android/server/wifi/WifiServiceImpl.java b/service/java/com/android/server/wifi/WifiServiceImpl.java
index 420f92366..c70247043 100644
--- a/service/java/com/android/server/wifi/WifiServiceImpl.java
+++ b/service/java/com/android/server/wifi/WifiServiceImpl.java
@@ -85,6 +85,7 @@ import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.AsyncChannel;
+import com.android.server.wifi.hotspot2.PasspointManager;
import com.android.server.wifi.util.WifiPermissionsUtil;
import java.io.BufferedReader;
@@ -160,6 +161,7 @@ public class WifiServiceImpl extends IWifiManager.Stub {
private WifiPermissionsUtil mWifiPermissionsUtil;
private final boolean mPermissionReviewRequired;
+ private final PasspointManager mPasspointManager;
/**
* Handles client connections
@@ -341,6 +343,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
mPermissionReviewRequired = Build.PERMISSIONS_REVIEW_REQUIRED
|| context.getResources().getBoolean(
com.android.internal.R.bool.config_permissionReviewRequired);
+ mPasspointManager = mWifiInjector.getPasspointManager();
+
enableVerboseLoggingInternal(getVerboseLoggingLevel());
}
@@ -1029,8 +1033,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
*/
@Override
public boolean addPasspointConfiguration(PasspointConfiguration config) {
- // TO BE IMPLEMENTED.
- return true;
+ enforceChangePermission();
+ return mPasspointManager.addProvider(config);
}
/**
@@ -1041,8 +1045,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
*/
@Override
public boolean removePasspointConfiguration(String fqdn) {
- // TO BE IMPLEMENTED.
- return true;
+ enforceChangePermission();
+ return mPasspointManager.removeProvider(fqdn);
}
/**
@@ -1052,8 +1056,8 @@ public class WifiServiceImpl extends IWifiManager.Stub {
*/
@Override
public List<PasspointConfiguration> getPasspointConfigurations() {
- // TO BE IMPLEMENTED.
- return null;
+ enforceAccessPermission();
+ return mPasspointManager.getProviderConfigs();
}
/**
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
index 762917170..2344440f2 100644
--- a/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -29,20 +29,33 @@ import static android.net.wifi.WifiManager.PASSPOINT_WNM_FRAME_RECEIVED_ACTION;
import android.content.Context;
import android.content.Intent;
+import android.net.wifi.hotspot2.PasspointConfiguration;
import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.SIMAccessor;
import com.android.server.wifi.WifiInjector;
import com.android.server.wifi.anqp.ANQPElement;
import com.android.server.wifi.anqp.Constants;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
/**
* Responsible for managing passpoint networks.
*/
public class PasspointManager {
- private final Context mContext;
+ private static final String TAG = "PasspointManager";
+
private final PasspointEventHandler mHandler;
+ private final SIMAccessor mSimAccessor;
+ private final Map<String, PasspointProvider> mProviders;
private class CallbackHandler implements PasspointEventHandler.Callbacks {
private final Context mContext;
@@ -91,9 +104,102 @@ public class PasspointManager {
}
}
- public PasspointManager(Context context, WifiInjector wifiInjector) {
- mContext = context;
+ public PasspointManager(Context context, WifiInjector wifiInjector, SIMAccessor simAccessor) {
mHandler = wifiInjector.makePasspointEventHandler(new CallbackHandler(context));
+ mSimAccessor = simAccessor;
+ mProviders = new HashMap<>();
+ // TODO(zqiu): load providers from the persistent storage.
+ }
+
+ /**
+ * Add or install a Passpoint provider with the given configuration.
+ *
+ * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name).
+ * In the case when there is an existing configuration with the same base
+ * domain, a provider with the new configuration will replace the existing provider.
+ *
+ * @param config Configuration of the Passpoint provider to be added
+ * @return true if provider is added, false otherwise
+ */
+ public boolean addProvider(PasspointConfiguration config) {
+ if (config == null) {
+ Log.e(TAG, "Configuration not provided");
+ return false;
+ }
+ if (!config.validate()) {
+ Log.e(TAG, "Invalid configuration");
+ return false;
+ }
+
+ // Verify IMSI against the IMSI of the installed SIM cards for SIM credential.
+ if (config.credential.simCredential != null) {
+ try {
+ if (mSimAccessor.getMatchingImsis(
+ new IMSIParameter(config.credential.simCredential.imsi)) == null) {
+ Log.e(TAG, "IMSI does not match any SIM card");
+ return false;
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ // TODO(b/32619189): install new key and certificates to the keystore.
+
+ // Detect existing configuration in the same base domain.
+ PasspointProvider existingProvider = findProviderInSameBaseDomain(config.homeSp.fqdn);
+ if (existingProvider != null) {
+ Log.d(TAG, "Replacing configuration for " + existingProvider.getConfig().homeSp.fqdn
+ + " with " + config.homeSp.fqdn);
+ // TODO(b/32619189): Remove existing key and certificates from the keystore.
+
+ mProviders.remove(existingProvider.getConfig().homeSp.fqdn);
+ }
+
+ // TODO(b/32714562): create/use a copy of configuration to avoid others from modifying it,
+ // since others might still have reference to it?
+ mProviders.put(config.homeSp.fqdn, new PasspointProvider(config));
+
+ // TODO(b/31065385): Persist updated providers configuration to the persistent storage.
+
+ return true;
+ }
+
+ /**
+ * Remove a Passpoint provider identified by the given FQDN.
+ *
+ * @param fqdn The FQDN of the provider to remove
+ * @return true if a provider is removed, false otherwise
+ */
+ public boolean removeProvider(String fqdn) {
+ if (!mProviders.containsKey(fqdn)) {
+ Log.e(TAG, "Config doesn't exist");
+ return false;
+ }
+
+ // TODO(b/32619189): Remove key and certificates from the keystore.
+
+ mProviders.remove(fqdn);
+ return true;
+ }
+
+ /**
+ * Return the installed Passpoint provider configurations.
+ *
+ * @return A list of {@link PasspointConfiguration} or null if none is installed
+ */
+ public List<PasspointConfiguration> getProviderConfigs() {
+ if (mProviders.size() == 0) {
+ return null;
+ }
+
+ List<PasspointConfiguration> configs = new ArrayList<>();
+ for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
+ // TODO(zqiu): return a copy of the configuration instead, to prevent others from
+ // modifying it?
+ configs.add(entry.getValue().getConfig());
+ }
+ return configs;
}
/**
@@ -130,4 +236,45 @@ public class PasspointManager {
public boolean queryPasspointIcon(long bssid, String fileName) {
return mHandler.requestIcon(bssid, fileName);
}
+
+ /**
+ * Find a provider that have FQDN in the same base domain as the given domain.
+ *
+ * @param domain The domain to be compared
+ * @return {@link PasspointProvider} if a match is found, null otherwise
+ */
+ private PasspointProvider findProviderInSameBaseDomain(String domain) {
+ for (Map.Entry<String, PasspointProvider> entry : mProviders.entrySet()) {
+ if (isSameBaseDomain(entry.getKey(), domain)) {
+ return entry.getValue();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check if one domain is the base domain for the other. For example, "test1.test.com"
+ * and "test.com" should return true.
+ *
+ * @param domain1 First domain to be compared
+ * @param domain2 Second domain to be compared
+ * @return true if one domain is a base domain for the other, false otherwise.
+ */
+ private static boolean isSameBaseDomain(String domain1, String domain2) {
+ if (domain1 == null || domain2 == null) {
+ return false;
+ }
+
+ List<String> labelList1 = Utils.splitDomain(domain1);
+ List<String> labelList2 = Utils.splitDomain(domain2);
+ Iterator<String> l1 = labelList1.iterator();
+ Iterator<String> l2 = labelList2.iterator();
+
+ while (l1.hasNext() && l2.hasNext()) {
+ if (!TextUtils.equals(l1.next(), l2.next())) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
new file mode 100644
index 000000000..2cf8c9f68
--- /dev/null
+++ b/service/java/com/android/server/wifi/hotspot2/PasspointProvider.java
@@ -0,0 +1,35 @@
+/*
+ * 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.hotspot2;
+
+import android.net.wifi.hotspot2.PasspointConfiguration;
+
+/**
+ * Abstraction for Passpoint service provider. This class contains the both static
+ * Passpoint configuration data and the runtime data (e.g. blacklisted SSIDs, statistics).
+ */
+public class PasspointProvider {
+ private final PasspointConfiguration mConfig;
+
+ public PasspointProvider(PasspointConfiguration config) {
+ mConfig = config;
+ }
+
+ public PasspointConfiguration getConfig() {
+ return mConfig;
+ }
+}
diff --git a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
index ffc212230..1d9f9372d 100644
--- a/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/hotspot2/PasspointManagerTest.java
@@ -22,16 +22,25 @@ import static android.net.wifi.WifiManager.EXTRA_PASSPOINT_ICON_FILE;
import static android.net.wifi.WifiManager.PASSPOINT_ICON_RECEIVED_ACTION;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import android.content.Context;
import android.content.Intent;
+import android.net.wifi.EAPConstants;
+import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.net.wifi.hotspot2.pps.Credential;
+import android.net.wifi.hotspot2.pps.HomeSP;
import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
+import com.android.server.wifi.FakeKeys;
+import com.android.server.wifi.IMSIParameter;
+import com.android.server.wifi.SIMAccessor;
import com.android.server.wifi.WifiInjector;
import org.junit.Before;
@@ -39,6 +48,9 @@ import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Unit tests for {@link com.android.server.wifi.hotspot2.PasspointManager}.
*/
@@ -46,17 +58,23 @@ import org.mockito.Mock;
public class PasspointManagerTest {
private static final long BSSID = 0x112233445566L;
private static final String ICON_FILENAME = "test";
+ private static final String TEST_FQDN = "test1.test.com";
+ private static final String TEST_FQDN1 = "test.com";
+ private static final String TEST_FRIENDLY_NAME = "friendly name";
+ private static final String TEST_REALM = "realm.test.com";
+ private static final String TEST_IMSI = "1234*";
@Mock Context mContext;
@Mock WifiInjector mWifiInjector;
@Mock PasspointEventHandler.Callbacks mCallbacks;
+ @Mock SIMAccessor mSimAccessor;
PasspointManager mManager;
/** Sets up test. */
@Before
public void setUp() throws Exception {
initMocks(this);
- mManager = new PasspointManager(mContext, mWifiInjector);
+ mManager = new PasspointManager(mContext, mWifiInjector, mSimAccessor);
ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
ArgumentCaptor.forClass(PasspointEventHandler.Callbacks.class);
verify(mWifiInjector).makePasspointEventHandler(callbacks.capture());
@@ -85,10 +103,24 @@ public class PasspointManagerTest {
}
/**
+ * Verify that the given Passpoint configuration matches the one that's added to
+ * the PasspointManager.
+ *
+ * @param expectedConfig The expected installed Passpoint configuration
+ */
+ private void verifyInstalledConfig(PasspointConfiguration expectedConfig) {
+ List<PasspointConfiguration> installedConfigs = mManager.getProviderConfigs();
+ assertEquals(1, installedConfigs.size());
+ assertEquals(expectedConfig, installedConfigs.get(0));
+ }
+
+ /**
* Validate the broadcast intent when icon file retrieval succeeded.
+ *
+ * @throws Exception
*/
@Test
- public void iconResponseSuccess() {
+ public void iconResponseSuccess() throws Exception {
byte[] iconData = new byte[] {0x00, 0x11};
mCallbacks.onIconResponse(BSSID, ICON_FILENAME, iconData);
verifyIconIntent(BSSID, ICON_FILENAME, iconData);
@@ -96,10 +128,172 @@ public class PasspointManagerTest {
/**
* Validate the broadcast intent when icon file retrieval failed.
+ *
+ * @throws Exception
*/
@Test
- public void iconResponseFailure() {
+ public void iconResponseFailure() throws Exception {
mCallbacks.onIconResponse(BSSID, ICON_FILENAME, null);
verifyIconIntent(BSSID, ICON_FILENAME, null);
}
+
+ /**
+ * Verify that adding a provider with a null configuration will fail.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void addProviderWithNullConfig() throws Exception {
+ assertFalse(mManager.addProvider(null));
+ }
+
+ /**
+ * Verify that adding a provider with a empty configuration will fail.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void addProviderWithEmptyConfig() throws Exception {
+ assertFalse(mManager.addProvider(new PasspointConfiguration()));
+ }
+
+ /**
+ * Verify taht adding a provider with an invalid credential will fail (using EAP-TLS
+ * for user credential).
+ *
+ * @throws Exception
+ */
+ @Test
+ public void addProviderWithInvalidCredential() throws Exception {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.homeSp = new HomeSP();
+ config.homeSp.fqdn = TEST_FQDN;
+ config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
+ config.credential = new Credential();
+ config.credential.realm = TEST_REALM;
+ config.credential.caCertificate = FakeKeys.CA_CERT0;
+ config.credential.userCredential = new Credential.UserCredential();
+ config.credential.userCredential.username = "username";
+ config.credential.userCredential.password = "password";
+ // EAP-TLS not allowed for user credential.
+ config.credential.userCredential.eapType = EAPConstants.EAP_TLS;
+ config.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
+ assertFalse(mManager.addProvider(config));
+ }
+
+ /**
+ * Verify that adding a provider with a valid configuration and user credential will succeed.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void addRemoveProviderWithValidUserCredential() throws Exception {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.homeSp = new HomeSP();
+ config.homeSp.fqdn = TEST_FQDN;
+ config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
+ config.credential = new Credential();
+ config.credential.realm = TEST_REALM;
+ config.credential.caCertificate = FakeKeys.CA_CERT0;
+ config.credential.userCredential = new Credential.UserCredential();
+ config.credential.userCredential.username = "username";
+ config.credential.userCredential.password = "password";
+ config.credential.userCredential.eapType = EAPConstants.EAP_TTLS;
+ config.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
+ assertTrue(mManager.addProvider(config));
+ verifyInstalledConfig(config);
+
+ // Remove the provider.
+ assertTrue(mManager.removeProvider(TEST_FQDN));
+ assertEquals(null, mManager.getProviderConfigs());
+ }
+
+ /**
+ * Verify that adding a provider with a valid configuration and SIM credential will succeed.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void addRemoveProviderWithValidSimCredential() throws Exception {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.homeSp = new HomeSP();
+ config.homeSp.fqdn = TEST_FQDN;
+ config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
+ config.credential = new Credential();
+ config.credential.realm = TEST_REALM;
+ config.credential.simCredential = new Credential.SimCredential();
+ config.credential.simCredential.imsi = TEST_IMSI;
+ config.credential.simCredential.eapType = EAPConstants.EAP_SIM;
+ when(mSimAccessor.getMatchingImsis(new IMSIParameter(TEST_IMSI)))
+ .thenReturn(new ArrayList<String>());
+ assertTrue(mManager.addProvider(config));
+ verifyInstalledConfig(config);
+
+ // Remove the provider.
+ assertTrue(mManager.removeProvider(TEST_FQDN));
+ assertEquals(null, mManager.getProviderConfigs());
+ }
+
+ /**
+ * Verify that adding a provider with an invalid SIM credential (configured IMSI doesn't
+ * match the IMSI of the installed SIM cards) will fail.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void addProviderWithValidSimCredentialWithInvalidIMSI() throws Exception {
+ PasspointConfiguration config = new PasspointConfiguration();
+ config.homeSp = new HomeSP();
+ config.homeSp.fqdn = TEST_FQDN;
+ config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
+ config.credential = new Credential();
+ config.credential.realm = TEST_REALM;
+ config.credential.simCredential = new Credential.SimCredential();
+ config.credential.simCredential.imsi = TEST_IMSI;
+ config.credential.simCredential.eapType = EAPConstants.EAP_SIM;
+ when(mSimAccessor.getMatchingImsis(new IMSIParameter(TEST_IMSI))).thenReturn(null);
+ assertFalse(mManager.addProvider(config));
+ }
+
+ /**
+ * Verify that adding a provider with the same base domain as the existing provider will
+ * succeed, and verify that the existing provider is replaced by the new provider with
+ * the new configuration.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void addProviderWithExistingConfig() throws Exception {
+ // Add a provider with the original configuration.
+ PasspointConfiguration origConfig = new PasspointConfiguration();
+ origConfig.homeSp = new HomeSP();
+ origConfig.homeSp.fqdn = TEST_FQDN;
+ origConfig.homeSp.friendlyName = TEST_FRIENDLY_NAME;
+ origConfig.credential = new Credential();
+ origConfig.credential.realm = TEST_REALM;
+ origConfig.credential.simCredential = new Credential.SimCredential();
+ origConfig.credential.simCredential.imsi = TEST_IMSI;
+ origConfig.credential.simCredential.eapType = EAPConstants.EAP_SIM;
+ when(mSimAccessor.getMatchingImsis(new IMSIParameter(TEST_IMSI)))
+ .thenReturn(new ArrayList<String>());
+ assertTrue(mManager.addProvider(origConfig));
+ verifyInstalledConfig(origConfig);
+
+ // Add another provider with the same base domain as the existing provider.
+ // This should replace the existing provider with the new configuration.
+ PasspointConfiguration newConfig = new PasspointConfiguration();
+ newConfig.homeSp = new HomeSP();
+ newConfig.homeSp.fqdn = TEST_FQDN1;
+ newConfig.homeSp.friendlyName = TEST_FRIENDLY_NAME;
+ newConfig.credential = new Credential();
+ newConfig.credential.realm = TEST_REALM;
+ newConfig.credential.caCertificate = FakeKeys.CA_CERT0;
+ newConfig.credential.userCredential = new Credential.UserCredential();
+ newConfig.credential.userCredential.username = "username";
+ newConfig.credential.userCredential.password = "password";
+ newConfig.credential.userCredential.eapType = EAPConstants.EAP_TTLS;
+ newConfig.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
+ assertTrue(mManager.addProvider(newConfig));
+ verifyInstalledConfig(newConfig);
+ }
}