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 android.net.wifi;
18
19import static org.junit.Assert.assertArrayEquals;
20import static org.junit.Assert.assertEquals;
21import static org.junit.Assert.assertFalse;
22import static org.junit.Assert.assertNotNull;
23import static org.junit.Assert.assertNotEquals;
24import static org.junit.Assert.assertNull;
25import static org.junit.Assert.assertTrue;
26
27import android.net.wifi.WifiEnterpriseConfig.Eap;
28import android.net.wifi.WifiEnterpriseConfig.Phase2;
29import android.os.Parcel;
30import android.security.Credentials;
31import android.support.test.filters.SmallTest;
32
33import org.junit.Before;
34import org.junit.Test;
35
36import java.security.PrivateKey;
37import java.security.cert.X509Certificate;
38
39
40/**
41 * Unit tests for {@link android.net.wifi.WifiEnterpriseConfig}.
42 */
43@SmallTest
44public class WifiEnterpriseConfigTest {
45    // Maintain a ground truth of the keystore uri prefix which is expected by wpa_supplicant.
46    public static final String KEYSTORE_URI = "keystore://";
47    public static final String CA_CERT_PREFIX = KEYSTORE_URI + Credentials.CA_CERTIFICATE;
48    public static final String KEYSTORES_URI = "keystores://";
49
50    private WifiEnterpriseConfig mEnterpriseConfig;
51
52    @Before
53    public void setUp() throws Exception {
54        mEnterpriseConfig = new WifiEnterpriseConfig();
55    }
56
57    @Test
58    public void testGetEmptyCaCertificate() {
59        // A newly-constructed WifiEnterpriseConfig object should have no CA certificate.
60        assertNull(mEnterpriseConfig.getCaCertificate());
61        assertNull(mEnterpriseConfig.getCaCertificates());
62        // Setting CA certificate to null explicitly.
63        mEnterpriseConfig.setCaCertificate(null);
64        assertNull(mEnterpriseConfig.getCaCertificate());
65        // Setting CA certificate to null using setCaCertificates().
66        mEnterpriseConfig.setCaCertificates(null);
67        assertNull(mEnterpriseConfig.getCaCertificates());
68        // Setting CA certificate to zero-length array.
69        mEnterpriseConfig.setCaCertificates(new X509Certificate[0]);
70        assertNull(mEnterpriseConfig.getCaCertificates());
71    }
72
73    @Test
74    public void testSetGetSingleCaCertificate() {
75        X509Certificate cert0 = FakeKeys.CA_CERT0;
76        mEnterpriseConfig.setCaCertificate(cert0);
77        assertEquals(mEnterpriseConfig.getCaCertificate(), cert0);
78    }
79
80    @Test
81    public void testSetGetMultipleCaCertificates() {
82        X509Certificate cert0 = FakeKeys.CA_CERT0;
83        X509Certificate cert1 = FakeKeys.CA_CERT1;
84        mEnterpriseConfig.setCaCertificates(new X509Certificate[] {cert0, cert1});
85        X509Certificate[] result = mEnterpriseConfig.getCaCertificates();
86        assertEquals(result.length, 2);
87        assertTrue(result[0] == cert0 && result[1] == cert1);
88    }
89
90    @Test
91    public void testSetClientKeyEntryWithNull() {
92        mEnterpriseConfig.setClientKeyEntry(null, null);
93        assertNull(mEnterpriseConfig.getClientCertificateChain());
94        assertNull(mEnterpriseConfig.getClientCertificate());
95        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
96        assertNull(mEnterpriseConfig.getClientCertificateChain());
97        assertNull(mEnterpriseConfig.getClientCertificate());
98
99        // Setting the client certificate to null should clear the existing chain.
100        PrivateKey clientKey = FakeKeys.RSA_KEY1;
101        X509Certificate clientCert0 = FakeKeys.CLIENT_CERT;
102        X509Certificate clientCert1 = FakeKeys.CA_CERT1;
103        mEnterpriseConfig.setClientKeyEntry(clientKey, clientCert0);
104        assertNotNull(mEnterpriseConfig.getClientCertificate());
105        mEnterpriseConfig.setClientKeyEntry(null, null);
106        assertNull(mEnterpriseConfig.getClientCertificate());
107        assertNull(mEnterpriseConfig.getClientCertificateChain());
108
109        // Setting the chain to null should clear the existing chain.
110        X509Certificate[] clientChain = new X509Certificate[] {clientCert0, clientCert1};
111        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
112        assertNotNull(mEnterpriseConfig.getClientCertificateChain());
113        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(null, null);
114        assertNull(mEnterpriseConfig.getClientCertificate());
115        assertNull(mEnterpriseConfig.getClientCertificateChain());
116    }
117
118    @Test
119    public void testSetClientCertificateChain() {
120        PrivateKey clientKey = FakeKeys.RSA_KEY1;
121        X509Certificate cert0 = FakeKeys.CLIENT_CERT;
122        X509Certificate cert1 = FakeKeys.CA_CERT1;
123        X509Certificate[] clientChain = new X509Certificate[] {cert0, cert1};
124        mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
125        X509Certificate[] result = mEnterpriseConfig.getClientCertificateChain();
126        assertEquals(result.length, 2);
127        assertTrue(result[0] == cert0 && result[1] == cert1);
128        assertTrue(mEnterpriseConfig.getClientCertificate() == cert0);
129    }
130
131    private boolean isClientCertificateChainInvalid(X509Certificate[] clientChain) {
132        boolean exceptionThrown = false;
133        try {
134            PrivateKey clientKey = FakeKeys.RSA_KEY1;
135            mEnterpriseConfig.setClientKeyEntryWithCertificateChain(clientKey, clientChain);
136        } catch (IllegalArgumentException e) {
137            exceptionThrown = true;
138        }
139        return exceptionThrown;
140    }
141
142    @Test
143    public void testSetInvalidClientCertificateChain() {
144        X509Certificate clientCert = FakeKeys.CLIENT_CERT;
145        X509Certificate caCert = FakeKeys.CA_CERT1;
146        assertTrue("Invalid client certificate",
147                isClientCertificateChainInvalid(new X509Certificate[] {caCert, caCert}));
148        assertTrue("Invalid CA certificate",
149                isClientCertificateChainInvalid(new X509Certificate[] {clientCert, clientCert}));
150        assertTrue("Both certificates invalid",
151                isClientCertificateChainInvalid(new X509Certificate[] {caCert, clientCert}));
152    }
153
154    @Test
155    public void testSaveSingleCaCertificateAlias() {
156        final String alias = "single_alias 0";
157        mEnterpriseConfig.setCaCertificateAliases(new String[] {alias});
158        assertEquals(getCaCertField(), CA_CERT_PREFIX + alias);
159    }
160
161    @Test
162    public void testLoadSingleCaCertificateAlias() {
163        final String alias = "single_alias 1";
164        setCaCertField(CA_CERT_PREFIX + alias);
165        String[] aliases = mEnterpriseConfig.getCaCertificateAliases();
166        assertEquals(aliases.length, 1);
167        assertEquals(aliases[0], alias);
168    }
169
170    @Test
171    public void testSaveMultipleCaCertificates() {
172        final String alias0 = "single_alias 0";
173        final String alias1 = "single_alias 1";
174        mEnterpriseConfig.setCaCertificateAliases(new String[] {alias0, alias1});
175        assertEquals(getCaCertField(), String.format("%s%s %s",
176                KEYSTORES_URI,
177                WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias0),
178                WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias1)));
179    }
180
181    @Test
182    public void testLoadMultipleCaCertificates() {
183        final String alias0 = "single_alias 0";
184        final String alias1 = "single_alias 1";
185        setCaCertField(String.format("%s%s %s",
186                KEYSTORES_URI,
187                WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias0),
188                WifiEnterpriseConfig.encodeCaCertificateAlias(Credentials.CA_CERTIFICATE + alias1)));
189        String[] aliases = mEnterpriseConfig.getCaCertificateAliases();
190        assertEquals(aliases.length, 2);
191        assertEquals(aliases[0], alias0);
192        assertEquals(aliases[1], alias1);
193    }
194
195    private String getCaCertField() {
196        return mEnterpriseConfig.getFieldValue(WifiEnterpriseConfig.CA_CERT_KEY);
197    }
198
199    private void setCaCertField(String value) {
200        mEnterpriseConfig.setFieldValue(WifiEnterpriseConfig.CA_CERT_KEY, value);
201    }
202
203    // Retrieves the value for a specific key supplied to wpa_supplicant.
204    private class SupplicantConfigExtractor implements WifiEnterpriseConfig.SupplicantSaver {
205        private String mValue = null;
206        private String mKey;
207
208        SupplicantConfigExtractor(String key) {
209            mKey = key;
210        }
211
212        @Override
213        public boolean saveValue(String key, String value) {
214            if (key.equals(mKey)) {
215                mValue = value;
216            }
217            return true;
218        }
219
220        public String getValue() {
221            return mValue;
222        }
223    }
224
225    private String getSupplicantEapMethod() {
226        SupplicantConfigExtractor entryExtractor = new SupplicantConfigExtractor(
227                WifiEnterpriseConfig.EAP_KEY);
228        mEnterpriseConfig.saveToSupplicant(entryExtractor);
229        return entryExtractor.getValue();
230    }
231
232    private String getSupplicantPhase2Method() {
233        SupplicantConfigExtractor entryExtractor = new SupplicantConfigExtractor(
234                WifiEnterpriseConfig.PHASE2_KEY);
235        mEnterpriseConfig.saveToSupplicant(entryExtractor);
236        return entryExtractor.getValue();
237    }
238
239    /** Verifies the default value for EAP outer and inner methods */
240    @Test
241    public void eapInnerDefault() {
242        assertEquals(null, getSupplicantEapMethod());
243        assertEquals(null, getSupplicantPhase2Method());
244    }
245
246    /** Verifies that the EAP inner method is reset when we switch to TLS */
247    @Test
248    public void eapPhase2MethodForTls() {
249        // Initially select an EAP method that supports an phase2.
250        mEnterpriseConfig.setEapMethod(Eap.PEAP);
251        mEnterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
252        assertEquals("PEAP", getSupplicantEapMethod());
253        assertEquals("\"auth=MSCHAPV2\"", getSupplicantPhase2Method());
254
255        // Change the EAP method to another type which supports a phase2.
256        mEnterpriseConfig.setEapMethod(Eap.TTLS);
257        assertEquals("TTLS", getSupplicantEapMethod());
258        assertEquals("\"auth=MSCHAPV2\"", getSupplicantPhase2Method());
259
260        // Change the EAP method to TLS which does not support a phase2.
261        mEnterpriseConfig.setEapMethod(Eap.TLS);
262        assertEquals(null, getSupplicantPhase2Method());
263    }
264
265    /** Verfies that the EAP inner method is reset when we switch phase2 to NONE */
266    @Test
267    public void eapPhase2None() {
268        // Initially select an EAP method that supports an phase2.
269        mEnterpriseConfig.setEapMethod(Eap.PEAP);
270        mEnterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
271        assertEquals("PEAP", getSupplicantEapMethod());
272        assertEquals("\"auth=MSCHAPV2\"", getSupplicantPhase2Method());
273
274        // Change the phase2 method to NONE and ensure the value is cleared.
275        mEnterpriseConfig.setPhase2Method(Phase2.NONE);
276        assertEquals(null, getSupplicantPhase2Method());
277    }
278
279    /** Verfies that the correct "autheap" parameter is supplied for TTLS/GTC. */
280    @Test
281    public void peapGtcToTtls() {
282        mEnterpriseConfig.setEapMethod(Eap.PEAP);
283        mEnterpriseConfig.setPhase2Method(Phase2.GTC);
284        assertEquals("PEAP", getSupplicantEapMethod());
285        assertEquals("\"auth=GTC\"", getSupplicantPhase2Method());
286
287        mEnterpriseConfig.setEapMethod(Eap.TTLS);
288        assertEquals("TTLS", getSupplicantEapMethod());
289        assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
290    }
291
292    /** Verfies that the correct "auth" parameter is supplied for PEAP/GTC. */
293    @Test
294    public void ttlsGtcToPeap() {
295        mEnterpriseConfig.setEapMethod(Eap.TTLS);
296        mEnterpriseConfig.setPhase2Method(Phase2.GTC);
297        assertEquals("TTLS", getSupplicantEapMethod());
298        assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
299
300        mEnterpriseConfig.setEapMethod(Eap.PEAP);
301        assertEquals("PEAP", getSupplicantEapMethod());
302        assertEquals("\"auth=GTC\"", getSupplicantPhase2Method());
303    }
304
305    /** Verfies PEAP/SIM, PEAP/AKA, PEAP/AKA'. */
306    @Test
307    public void peapSimAkaAkaPrime() {
308        mEnterpriseConfig.setEapMethod(Eap.PEAP);
309        mEnterpriseConfig.setPhase2Method(Phase2.SIM);
310        assertEquals("PEAP", getSupplicantEapMethod());
311        assertEquals("\"auth=SIM\"", getSupplicantPhase2Method());
312
313        mEnterpriseConfig.setPhase2Method(Phase2.AKA);
314        assertEquals("\"auth=AKA\"", getSupplicantPhase2Method());
315
316        mEnterpriseConfig.setPhase2Method(Phase2.AKA_PRIME);
317        assertEquals("\"auth=AKA'\"", getSupplicantPhase2Method());
318    }
319
320    /**
321     * Verifies that the copy constructor preseves both the masked password and inner method
322     * information.
323     */
324    @Test
325    public void copyConstructor() {
326        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
327        enterpriseConfig.setPassword("*");
328        enterpriseConfig.setEapMethod(Eap.TTLS);
329        enterpriseConfig.setPhase2Method(Phase2.GTC);
330        mEnterpriseConfig = new WifiEnterpriseConfig(enterpriseConfig);
331        assertEquals("TTLS", getSupplicantEapMethod());
332        assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
333        assertEquals("*", mEnterpriseConfig.getPassword());
334    }
335
336    /**
337     * Verifies that the copy from external ignores masked passwords and preserves the
338     * inner method information.
339     */
340    @Test
341    public void copyFromExternal() {
342        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
343        enterpriseConfig.setPassword("*");
344        enterpriseConfig.setEapMethod(Eap.TTLS);
345        enterpriseConfig.setPhase2Method(Phase2.GTC);
346        mEnterpriseConfig = new WifiEnterpriseConfig();
347        mEnterpriseConfig.copyFromExternal(enterpriseConfig, "*");
348        assertEquals("TTLS", getSupplicantEapMethod());
349        assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
350        assertNotEquals("*", mEnterpriseConfig.getPassword());
351    }
352
353    /** Verfies that parceling a WifiEnterpriseConfig preseves method information. */
354    @Test
355    public void parcelConstructor() {
356        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
357        enterpriseConfig.setEapMethod(Eap.TTLS);
358        enterpriseConfig.setPhase2Method(Phase2.GTC);
359        Parcel parcel = Parcel.obtain();
360        enterpriseConfig.writeToParcel(parcel, 0);
361        parcel.setDataPosition(0);  // Allow parcel to be read from the beginning.
362        mEnterpriseConfig = WifiEnterpriseConfig.CREATOR.createFromParcel(parcel);
363        assertEquals("TTLS", getSupplicantEapMethod());
364        assertEquals("\"autheap=GTC\"", getSupplicantPhase2Method());
365    }
366
367    /**
368     * Verifies that parceling a WifiEnterpriseConfig preserves the key
369     * and certificates information.
370     */
371    @Test
372    public void parcelConfigWithKeyAndCerts() throws Exception {
373        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
374        PrivateKey clientKey = FakeKeys.RSA_KEY1;
375        X509Certificate clientCert = FakeKeys.CLIENT_CERT;
376        X509Certificate[] caCerts = new X509Certificate[] {FakeKeys.CA_CERT0, FakeKeys.CA_CERT1};
377        enterpriseConfig.setClientKeyEntry(clientKey, clientCert);
378        enterpriseConfig.setCaCertificates(caCerts);
379        Parcel parcel = Parcel.obtain();
380        enterpriseConfig.writeToParcel(parcel, 0);
381
382        parcel.setDataPosition(0);  // Allow parcel to be read from the beginning.
383        mEnterpriseConfig = WifiEnterpriseConfig.CREATOR.createFromParcel(parcel);
384        PrivateKey actualClientKey = mEnterpriseConfig.getClientPrivateKey();
385        X509Certificate actualClientCert = mEnterpriseConfig.getClientCertificate();
386        X509Certificate[] actualCaCerts = mEnterpriseConfig.getCaCertificates();
387
388        /* Verify client private key. */
389        assertNotNull(actualClientKey);
390        assertEquals(clientKey.getAlgorithm(), actualClientKey.getAlgorithm());
391        assertArrayEquals(clientKey.getEncoded(), actualClientKey.getEncoded());
392
393        /* Verify client certificate. */
394        assertNotNull(actualClientCert);
395        assertArrayEquals(clientCert.getEncoded(), actualClientCert.getEncoded());
396
397        /* Verify CA certificates. */
398        assertNotNull(actualCaCerts);
399        assertEquals(caCerts.length, actualCaCerts.length);
400        for (int i = 0; i < caCerts.length; i++) {
401            assertNotNull(actualCaCerts[i]);
402            assertArrayEquals(caCerts[i].getEncoded(), actualCaCerts[i].getEncoded());
403        }
404    }
405
406    /** Verifies proper operation of the getKeyId() method. */
407    @Test
408    public void getKeyId() {
409        assertEquals("NULL", mEnterpriseConfig.getKeyId(null));
410        WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
411        enterpriseConfig.setEapMethod(Eap.TTLS);
412        enterpriseConfig.setPhase2Method(Phase2.GTC);
413        assertEquals("TTLS_GTC", mEnterpriseConfig.getKeyId(enterpriseConfig));
414        mEnterpriseConfig.setEapMethod(Eap.PEAP);
415        mEnterpriseConfig.setPhase2Method(Phase2.MSCHAPV2);
416        assertEquals("PEAP_MSCHAPV2", mEnterpriseConfig.getKeyId(enterpriseConfig));
417    }
418
419    /** Verifies that passwords are not displayed in toString. */
420    @Test
421    public void passwordNotInToString() {
422        String password = "supersecret";
423        mEnterpriseConfig.setPassword(password);
424        assertFalse(mEnterpriseConfig.toString().contains(password));
425    }
426}
427