PasspointManagerTest.java revision 87c6f1b149804685e46c18d2ad11262f611c9255
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi.hotspot2;
18
19import static android.net.wifi.WifiManager.EXTRA_PASSPOINT_ICON_BSSID;
20import static android.net.wifi.WifiManager.EXTRA_PASSPOINT_ICON_DATA;
21import static android.net.wifi.WifiManager.EXTRA_PASSPOINT_ICON_FILE;
22import static android.net.wifi.WifiManager.PASSPOINT_ICON_RECEIVED_ACTION;
23
24import static org.junit.Assert.assertEquals;
25import static org.junit.Assert.assertFalse;
26import static org.junit.Assert.assertTrue;
27import static org.mockito.Mockito.any;
28import static org.mockito.Mockito.anyLong;
29import static org.mockito.Mockito.anyMap;
30import static org.mockito.Mockito.eq;
31import static org.mockito.Mockito.mock;
32import static org.mockito.Mockito.verify;
33import static org.mockito.Mockito.when;
34import static org.mockito.MockitoAnnotations.initMocks;
35
36import android.content.Context;
37import android.content.Intent;
38import android.net.wifi.EAPConstants;
39import android.net.wifi.hotspot2.PasspointConfiguration;
40import android.net.wifi.hotspot2.pps.Credential;
41import android.net.wifi.hotspot2.pps.HomeSP;
42import android.os.UserHandle;
43import android.test.suitebuilder.annotation.SmallTest;
44import android.util.Pair;
45
46import com.android.server.wifi.Clock;
47import com.android.server.wifi.FakeKeys;
48import com.android.server.wifi.IMSIParameter;
49import com.android.server.wifi.SIMAccessor;
50import com.android.server.wifi.ScanDetail;
51import com.android.server.wifi.WifiKeyStore;
52import com.android.server.wifi.WifiNative;
53
54import org.junit.Before;
55import org.junit.Test;
56import org.mockito.ArgumentCaptor;
57import org.mockito.Mock;
58
59import java.util.ArrayList;
60import java.util.List;
61
62/**
63 * Unit tests for {@link com.android.server.wifi.hotspot2.PasspointManager}.
64 */
65@SmallTest
66public class PasspointManagerTest {
67    private static final long BSSID = 0x112233445566L;
68    private static final String ICON_FILENAME = "test";
69    private static final String TEST_FQDN = "test1.test.com";
70    private static final String TEST_FRIENDLY_NAME = "friendly name";
71    private static final String TEST_REALM = "realm.test.com";
72    private static final String TEST_IMSI = "1234*";
73    private static final IMSIParameter TEST_IMSI_PARAM = IMSIParameter.build(TEST_IMSI);
74
75    private static final String TEST_SSID = "TestSSID";
76    private static final long TEST_BSSID = 0x1234L;
77    private static final long TEST_HESSID = 0x5678L;
78    private static final int TEST_ANQP_DOMAIN_ID = 1;
79
80    @Mock Context mContext;
81    @Mock WifiNative mWifiNative;
82    @Mock WifiKeyStore mWifiKeyStore;
83    @Mock Clock mClock;
84    @Mock SIMAccessor mSimAccessor;
85    @Mock PasspointObjectFactory mObjectFactory;
86    @Mock PasspointEventHandler.Callbacks mCallbacks;
87    @Mock AnqpCache mAnqpCache;
88    PasspointManager mManager;
89
90    /** Sets up test. */
91    @Before
92    public void setUp() throws Exception {
93        initMocks(this);
94        when(mObjectFactory.makeAnqpCache(mClock)).thenReturn(mAnqpCache);
95        mManager = new PasspointManager(mContext, mWifiNative, mWifiKeyStore, mClock,
96                mSimAccessor, mObjectFactory);
97        ArgumentCaptor<PasspointEventHandler.Callbacks> callbacks =
98                ArgumentCaptor.forClass(PasspointEventHandler.Callbacks.class);
99        verify(mObjectFactory).makePasspointEventHandler(any(WifiNative.class),
100                                                         callbacks.capture());
101        mCallbacks = callbacks.getValue();
102    }
103
104    /**
105     * Verify PASSPOINT_ICON_RECEIVED_ACTION broadcast intent.
106     * @param bssid BSSID of the AP
107     * @param fileName Name of the icon file
108     * @param data icon data byte array
109     */
110    private void verifyIconIntent(long bssid, String fileName, byte[] data) {
111        ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
112        verify(mContext).sendBroadcastAsUser(intent.capture(), eq(UserHandle.ALL));
113        assertEquals(PASSPOINT_ICON_RECEIVED_ACTION, intent.getValue().getAction());
114        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_PASSPOINT_ICON_BSSID));
115        assertEquals(bssid, intent.getValue().getExtras().getLong(EXTRA_PASSPOINT_ICON_BSSID));
116        assertTrue(intent.getValue().getExtras().containsKey(EXTRA_PASSPOINT_ICON_FILE));
117        assertEquals(fileName, intent.getValue().getExtras().getString(EXTRA_PASSPOINT_ICON_FILE));
118        if (data != null) {
119            assertTrue(intent.getValue().getExtras().containsKey(EXTRA_PASSPOINT_ICON_DATA));
120            assertEquals(data,
121                         intent.getValue().getExtras().getByteArray(EXTRA_PASSPOINT_ICON_DATA));
122        }
123    }
124
125    /**
126     * Verify that the given Passpoint configuration matches the one that's added to
127     * the PasspointManager.
128     *
129     * @param expectedConfig The expected installed Passpoint configuration
130     */
131    private void verifyInstalledConfig(PasspointConfiguration expectedConfig) {
132        List<PasspointConfiguration> installedConfigs = mManager.getProviderConfigs();
133        assertEquals(1, installedConfigs.size());
134        assertEquals(expectedConfig, installedConfigs.get(0));
135    }
136
137    /**
138     * Create a mock PasspointProvider with default expectations.
139     *
140     * @param config The configuration associated with the provider
141     * @return {@link com.android.server.wifi.hotspot2.PasspointProvider}
142     */
143    private PasspointProvider createMockProvider(PasspointConfiguration config) {
144        PasspointProvider provider = mock(PasspointProvider.class);
145        when(provider.installCertsAndKeys()).thenReturn(true);
146        when(provider.getConfig()).thenReturn(config);
147        return provider;
148    }
149
150    /**
151     * Helper function for adding a test provider to the manager.  Return the mock
152     * provider that's added to the manager.
153     *
154     * @return {@link PasspointProvider}
155     */
156    private PasspointProvider addTestProvider() {
157        PasspointConfiguration config = new PasspointConfiguration();
158        config.homeSp = new HomeSP();
159        config.homeSp.fqdn = TEST_FQDN;
160        config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
161        config.credential = new Credential();
162        config.credential.realm = TEST_REALM;
163        config.credential.caCertificate = FakeKeys.CA_CERT0;
164        config.credential.userCredential = new Credential.UserCredential();
165        config.credential.userCredential.username = "username";
166        config.credential.userCredential.password = "password";
167        config.credential.userCredential.eapType = EAPConstants.EAP_TTLS;
168        config.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
169        PasspointProvider provider = createMockProvider(config);
170        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
171                eq(mSimAccessor), anyLong())).thenReturn(provider);
172        assertTrue(mManager.addOrUpdateProvider(config));
173
174        return provider;
175    }
176
177    /**
178     * Helper function for creating a mock ScanDetail.
179     *
180     * @return {@link ScanDetail}
181     */
182    private ScanDetail createMockScanDetail() {
183        NetworkDetail networkDetail = mock(NetworkDetail.class);
184        when(networkDetail.getSSID()).thenReturn(TEST_SSID);
185        when(networkDetail.getBSSID()).thenReturn(TEST_BSSID);
186        when(networkDetail.getHESSID()).thenReturn(TEST_HESSID);
187        when(networkDetail.getAnqpDomainID()).thenReturn(TEST_ANQP_DOMAIN_ID);
188
189        ScanDetail scanDetail = mock(ScanDetail.class);
190        when(scanDetail.getNetworkDetail()).thenReturn(networkDetail);
191        return scanDetail;
192    }
193
194    /**
195     * Validate the broadcast intent when icon file retrieval succeeded.
196     *
197     * @throws Exception
198     */
199    @Test
200    public void iconResponseSuccess() throws Exception {
201        byte[] iconData = new byte[] {0x00, 0x11};
202        mCallbacks.onIconResponse(BSSID, ICON_FILENAME, iconData);
203        verifyIconIntent(BSSID, ICON_FILENAME, iconData);
204    }
205
206    /**
207     * Validate the broadcast intent when icon file retrieval failed.
208     *
209     * @throws Exception
210     */
211    @Test
212    public void iconResponseFailure() throws Exception {
213        mCallbacks.onIconResponse(BSSID, ICON_FILENAME, null);
214        verifyIconIntent(BSSID, ICON_FILENAME, null);
215    }
216
217    /**
218     * Verify that adding a provider with a null configuration will fail.
219     *
220     * @throws Exception
221     */
222    @Test
223    public void addProviderWithNullConfig() throws Exception {
224        assertFalse(mManager.addOrUpdateProvider(null));
225    }
226
227    /**
228     * Verify that adding a provider with a empty configuration will fail.
229     *
230     * @throws Exception
231     */
232    @Test
233    public void addProviderWithEmptyConfig() throws Exception {
234        assertFalse(mManager.addOrUpdateProvider(new PasspointConfiguration()));
235    }
236
237    /**
238     * Verify taht adding a provider with an invalid credential will fail (using EAP-TLS
239     * for user credential).
240     *
241     * @throws Exception
242     */
243    @Test
244    public void addProviderWithInvalidCredential() throws Exception {
245        PasspointConfiguration config = new PasspointConfiguration();
246        config.homeSp = new HomeSP();
247        config.homeSp.fqdn = TEST_FQDN;
248        config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
249        config.credential = new Credential();
250        config.credential.realm = TEST_REALM;
251        config.credential.caCertificate = FakeKeys.CA_CERT0;
252        config.credential.userCredential = new Credential.UserCredential();
253        config.credential.userCredential.username = "username";
254        config.credential.userCredential.password = "password";
255        // EAP-TLS not allowed for user credential.
256        config.credential.userCredential.eapType = EAPConstants.EAP_TLS;
257        config.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
258        assertFalse(mManager.addOrUpdateProvider(config));
259    }
260
261    /**
262     * Verify that adding a provider with a valid configuration and user credential will succeed.
263     *
264     * @throws Exception
265     */
266    @Test
267    public void addRemoveProviderWithValidUserCredential() throws Exception {
268        PasspointConfiguration config = new PasspointConfiguration();
269        config.homeSp = new HomeSP();
270        config.homeSp.fqdn = TEST_FQDN;
271        config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
272        config.credential = new Credential();
273        config.credential.realm = TEST_REALM;
274        config.credential.caCertificate = FakeKeys.CA_CERT0;
275        config.credential.userCredential = new Credential.UserCredential();
276        config.credential.userCredential.username = "username";
277        config.credential.userCredential.password = "password";
278        config.credential.userCredential.eapType = EAPConstants.EAP_TTLS;
279        config.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
280        PasspointProvider provider = createMockProvider(config);
281        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
282                eq(mSimAccessor), anyLong())).thenReturn(provider);
283        assertTrue(mManager.addOrUpdateProvider(config));
284        verifyInstalledConfig(config);
285
286        // Remove the provider.
287        assertTrue(mManager.removeProvider(TEST_FQDN));
288        verify(provider).uninstallCertsAndKeys();
289        assertTrue(mManager.getProviderConfigs().isEmpty());
290    }
291
292    /**
293     * Verify that adding a provider with a valid configuration and SIM credential will succeed.
294     *
295     * @throws Exception
296     */
297    @Test
298    public void addRemoveProviderWithValidSimCredential() throws Exception {
299        PasspointConfiguration config = new PasspointConfiguration();
300        config.homeSp = new HomeSP();
301        config.homeSp.fqdn = TEST_FQDN;
302        config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
303        config.credential = new Credential();
304        config.credential.realm = TEST_REALM;
305        config.credential.simCredential = new Credential.SimCredential();
306        config.credential.simCredential.imsi = TEST_IMSI;
307        config.credential.simCredential.eapType = EAPConstants.EAP_SIM;
308        when(mSimAccessor.getMatchingImsis(TEST_IMSI_PARAM)).thenReturn(new ArrayList<String>());
309        PasspointProvider provider = createMockProvider(config);
310        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
311                eq(mSimAccessor), anyLong())).thenReturn(provider);
312        assertTrue(mManager.addOrUpdateProvider(config));
313        verifyInstalledConfig(config);
314
315        // Remove the provider.
316        assertTrue(mManager.removeProvider(TEST_FQDN));
317        verify(provider).uninstallCertsAndKeys();
318        assertTrue(mManager.getProviderConfigs().isEmpty());
319    }
320
321    /**
322     * Verify that adding a provider with an invalid SIM credential (configured IMSI doesn't
323     * match the IMSI of the installed SIM cards) will fail.
324     *
325     * @throws Exception
326     */
327    @Test
328    public void addProviderWithValidSimCredentialWithInvalidIMSI() throws Exception {
329        PasspointConfiguration config = new PasspointConfiguration();
330        config.homeSp = new HomeSP();
331        config.homeSp.fqdn = TEST_FQDN;
332        config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
333        config.credential = new Credential();
334        config.credential.realm = TEST_REALM;
335        config.credential.simCredential = new Credential.SimCredential();
336        config.credential.simCredential.imsi = TEST_IMSI;
337        config.credential.simCredential.eapType = EAPConstants.EAP_SIM;
338        when(mSimAccessor.getMatchingImsis(TEST_IMSI_PARAM)).thenReturn(null);
339        assertFalse(mManager.addOrUpdateProvider(config));
340    }
341
342    /**
343     * Verify that adding a provider with the same base domain as the existing provider will
344     * succeed, and verify that the existing provider is replaced by the new provider with
345     * the new configuration.
346     *
347     * @throws Exception
348     */
349    @Test
350    public void addProviderWithExistingConfig() throws Exception {
351        // Add a provider with the original configuration.
352        PasspointConfiguration origConfig = new PasspointConfiguration();
353        origConfig.homeSp = new HomeSP();
354        origConfig.homeSp.fqdn = TEST_FQDN;
355        origConfig.homeSp.friendlyName = TEST_FRIENDLY_NAME;
356        origConfig.credential = new Credential();
357        origConfig.credential.realm = TEST_REALM;
358        origConfig.credential.simCredential = new Credential.SimCredential();
359        origConfig.credential.simCredential.imsi = TEST_IMSI;
360        origConfig.credential.simCredential.eapType = EAPConstants.EAP_SIM;
361        when(mSimAccessor.getMatchingImsis(TEST_IMSI_PARAM)).thenReturn(new ArrayList<String>());
362        PasspointProvider origProvider = createMockProvider(origConfig);
363        when(mObjectFactory.makePasspointProvider(eq(origConfig), eq(mWifiKeyStore),
364                eq(mSimAccessor), anyLong())).thenReturn(origProvider);
365        assertTrue(mManager.addOrUpdateProvider(origConfig));
366        verifyInstalledConfig(origConfig);
367
368        // Add another provider with the same base domain as the existing provider.
369        // This should replace the existing provider with the new configuration.
370        PasspointConfiguration newConfig = new PasspointConfiguration();
371        newConfig.homeSp = new HomeSP();
372        newConfig.homeSp.fqdn = TEST_FQDN;
373        newConfig.homeSp.friendlyName = TEST_FRIENDLY_NAME;
374        newConfig.credential = new Credential();
375        newConfig.credential.realm = TEST_REALM;
376        newConfig.credential.caCertificate = FakeKeys.CA_CERT0;
377        newConfig.credential.userCredential = new Credential.UserCredential();
378        newConfig.credential.userCredential.username = "username";
379        newConfig.credential.userCredential.password = "password";
380        newConfig.credential.userCredential.eapType = EAPConstants.EAP_TTLS;
381        newConfig.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
382        PasspointProvider newProvider = createMockProvider(newConfig);
383        when(mObjectFactory.makePasspointProvider(eq(newConfig), eq(mWifiKeyStore),
384                eq(mSimAccessor), anyLong())).thenReturn(newProvider);
385        assertTrue(mManager.addOrUpdateProvider(newConfig));
386        verifyInstalledConfig(newConfig);
387    }
388
389    /**
390     * Verify that adding a provider will fail when failing to install certificates and
391     * key to the keystore.
392     *
393     * @throws Exception
394     */
395    @Test
396    public void addProviderOnKeyInstallationFailiure() throws Exception {
397        PasspointConfiguration config = new PasspointConfiguration();
398        config.homeSp = new HomeSP();
399        config.homeSp.fqdn = TEST_FQDN;
400        config.homeSp.friendlyName = TEST_FRIENDLY_NAME;
401        config.credential = new Credential();
402        config.credential.realm = TEST_REALM;
403        config.credential.caCertificate = FakeKeys.CA_CERT0;
404        config.credential.userCredential = new Credential.UserCredential();
405        config.credential.userCredential.username = "username";
406        config.credential.userCredential.password = "password";
407        config.credential.userCredential.eapType = EAPConstants.EAP_TTLS;
408        config.credential.userCredential.nonEapInnerMethod = "MS-CHAP";
409        PasspointProvider provider = mock(PasspointProvider.class);
410        when(provider.installCertsAndKeys()).thenReturn(false);
411        when(mObjectFactory.makePasspointProvider(eq(config), eq(mWifiKeyStore),
412                eq(mSimAccessor), anyLong())).thenReturn(provider);
413        assertFalse(mManager.addOrUpdateProvider(config));
414    }
415
416    /**
417     * Verify that removing a non-existing provider will fail.
418     *
419     * @throws Exception
420     */
421    @Test
422    public void removeNonExistingProvider() throws Exception {
423        assertFalse(mManager.removeProvider(TEST_FQDN));
424    }
425
426    /**
427     * Verify that an empty list will be returned when no providers are installed.
428     *
429     * @throws Exception
430     */
431    @Test
432    public void matchProviderWithNoProvidersInstalled() throws Exception {
433        List<Pair<PasspointProvider, PasspointMatch>> result =
434                mManager.matchProvider(createMockScanDetail());
435        assertTrue(result.isEmpty());
436    }
437
438    /**
439     * Verify that an empty list will be returned when ANQP entry doesn't exist in the cache.
440     *
441     * @throws Exception
442     */
443    @Test
444    public void matchProviderWithAnqpCacheMissed() throws Exception {
445        addTestProvider();
446
447        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
448                TEST_ANQP_DOMAIN_ID);
449        when(mAnqpCache.getEntry(anqpKey)).thenReturn(null);
450        List<Pair<PasspointProvider, PasspointMatch>> result =
451                mManager.matchProvider(createMockScanDetail());
452        assertTrue(result.isEmpty());
453    }
454
455    /**
456     * Verify that the returned list will contained an expected provider when a HomeProvider
457     * is matched.
458     *
459     * @throws Exception
460     */
461    @Test
462    public void matchProviderAsHomeProvider() throws Exception {
463        PasspointProvider provider = addTestProvider();
464        ANQPData entry = new ANQPData(mClock, null);
465        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
466                TEST_ANQP_DOMAIN_ID);
467
468        when(mAnqpCache.getEntry(anqpKey)).thenReturn(entry);
469        when(provider.match(anyMap())).thenReturn(PasspointMatch.HomeProvider);
470        List<Pair<PasspointProvider, PasspointMatch>> result =
471                mManager.matchProvider(createMockScanDetail());
472        assertEquals(1, result.size());
473        assertEquals(PasspointMatch.HomeProvider, result.get(0).second);
474        assertEquals(TEST_FQDN, provider.getConfig().homeSp.fqdn);
475    }
476
477    /**
478     * Verify that the returned list will contained an expected provider when a RoamingProvider
479     * is matched.
480     *
481     * @throws Exception
482     */
483    @Test
484    public void matchProviderAsRoamingProvider() throws Exception {
485        PasspointProvider provider = addTestProvider();
486        ANQPData entry = new ANQPData(mClock, null);
487        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
488                TEST_ANQP_DOMAIN_ID);
489
490        when(mAnqpCache.getEntry(anqpKey)).thenReturn(entry);
491        when(provider.match(anyMap())).thenReturn(PasspointMatch.RoamingProvider);
492        List<Pair<PasspointProvider, PasspointMatch>> result =
493                mManager.matchProvider(createMockScanDetail());
494        assertEquals(1, result.size());
495        assertEquals(PasspointMatch.RoamingProvider, result.get(0).second);
496        assertEquals(TEST_FQDN, provider.getConfig().homeSp.fqdn);
497    }
498
499    /**
500     * Verify that an empty list will be returned when there is no matching provider.
501     *
502     * @throws Exception
503     */
504    @Test
505    public void matchProviderWithNoMatch() throws Exception {
506        PasspointProvider provider = addTestProvider();
507        ANQPData entry = new ANQPData(mClock, null);
508        ANQPNetworkKey anqpKey = ANQPNetworkKey.buildKey(TEST_SSID, TEST_BSSID, TEST_HESSID,
509                TEST_ANQP_DOMAIN_ID);
510
511        when(mAnqpCache.getEntry(anqpKey)).thenReturn(entry);
512        when(provider.match(anyMap())).thenReturn(PasspointMatch.None);
513        List<Pair<PasspointProvider, PasspointMatch>> result =
514                mManager.matchProvider(createMockScanDetail());
515        assertEquals(0, result.size());
516    }
517
518    /**
519     * Verify the expectations for sweepCache.
520     *
521     * @throws Exception
522     */
523    @Test
524    public void sweepCache() throws Exception {
525        mManager.sweepCache();
526        verify(mAnqpCache).sweep();
527    }
528}
529