1 /*
2  * Copyright 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 
17 package com.android.managedprovisioning.task.wifi;
18 
19 import android.annotation.Nullable;
20 import android.net.IpConfiguration;
21 import android.net.IpConfiguration.ProxySettings;
22 import android.net.ProxyInfo;
23 import android.net.Uri;
24 import android.net.wifi.WifiConfiguration;
25 import android.net.wifi.WifiEnterpriseConfig;
26 import android.text.TextUtils;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.managedprovisioning.common.ProvisionLogger;
30 import com.android.managedprovisioning.model.WifiInfo;
31 
32 import java.io.ByteArrayInputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.nio.charset.StandardCharsets;
36 import java.security.Key;
37 import java.security.KeyStore;
38 import java.security.KeyStoreException;
39 import java.security.NoSuchAlgorithmException;
40 import java.security.PrivateKey;
41 import java.security.UnrecoverableKeyException;
42 import java.security.cert.Certificate;
43 import java.security.cert.CertificateException;
44 import java.security.cert.CertificateFactory;
45 import java.security.cert.X509Certificate;
46 import java.util.Arrays;
47 import java.util.Base64;
48 import java.util.Collections;
49 import java.util.HashMap;
50 import java.util.List;
51 import java.util.Map;
52 
53 /**
54  * Utility class for configuring a new {@link WifiConfiguration} object from the provisioning
55  * parameters represented via {@link WifiInfo}.
56  */
57 public class WifiConfigurationProvider {
58 
59     @VisibleForTesting
60     static final String WPA = "WPA";
61     @VisibleForTesting
62     static final String WEP = "WEP";
63     @VisibleForTesting
64     static final String EAP = "EAP";
65     @VisibleForTesting
66     static final String NONE = "NONE";
67     @VisibleForTesting
68     static final char[] PASSWORD = {};
69     public static final String KEYSTORE_TYPE_PKCS12 = "PKCS12";
70     private static Map<String, Integer> EAP_METHODS = buildEapMethodsMap();
71     private static Map<String, Integer> PHASE2_AUTH = buildPhase2AuthMap();
72 
buildEapMethodsMap()73     private static Map<String, Integer> buildEapMethodsMap() {
74         Map<String, Integer> map = new HashMap<>();
75         map.put("PEAP", WifiEnterpriseConfig.Eap.PEAP);
76         map.put("TLS", WifiEnterpriseConfig.Eap.TLS);
77         map.put("TTLS", WifiEnterpriseConfig.Eap.TTLS);
78         map.put("PWD", WifiEnterpriseConfig.Eap.PWD);
79         map.put("SIM", WifiEnterpriseConfig.Eap.SIM);
80         map.put("AKA", WifiEnterpriseConfig.Eap.AKA);
81         map.put("AKA_PRIME", WifiEnterpriseConfig.Eap.AKA_PRIME);
82         return map;
83     }
84 
buildPhase2AuthMap()85     private static Map<String, Integer> buildPhase2AuthMap() {
86         Map<String, Integer> map = new HashMap<>();
87         map.put(null, WifiEnterpriseConfig.Phase2.NONE);
88         map.put("", WifiEnterpriseConfig.Phase2.NONE);
89         map.put("NONE", WifiEnterpriseConfig.Phase2.NONE);
90         map.put("PAP", WifiEnterpriseConfig.Phase2.PAP);
91         map.put("MSCHAP", WifiEnterpriseConfig.Phase2.MSCHAP);
92         map.put("MSCHAPV2", WifiEnterpriseConfig.Phase2.MSCHAPV2);
93         map.put("GTC", WifiEnterpriseConfig.Phase2.GTC);
94         map.put("SIM", WifiEnterpriseConfig.Phase2.SIM);
95         map.put("AKA", WifiEnterpriseConfig.Phase2.AKA);
96         map.put("AKA_PRIME", WifiEnterpriseConfig.Phase2.AKA_PRIME);
97         return map;
98     }
99 
100     /**
101      * Create a {@link WifiConfiguration} object from the internal representation given via
102      * {@link WifiInfo}.
103      */
generateWifiConfiguration(WifiInfo wifiInfo)104     public WifiConfiguration generateWifiConfiguration(WifiInfo wifiInfo) {
105         WifiConfiguration wifiConf = new WifiConfiguration();
106         wifiConf.SSID = wifiInfo.ssid;
107         wifiConf.status = WifiConfiguration.Status.ENABLED;
108         wifiConf.hiddenSSID = wifiInfo.hidden;
109         wifiConf.userApproved = WifiConfiguration.USER_APPROVED;
110         String securityType = wifiInfo.securityType != null ? wifiInfo.securityType : NONE;
111         switch (securityType) {
112             case WPA:
113                 updateForWPAConfiguration(wifiConf, wifiInfo.password);
114                 break;
115             case WEP:
116                 updateForWEPConfiguration(wifiConf, wifiInfo.password);
117                 break;
118             case EAP:
119                 maybeUpdateForEAPConfiguration(wifiConf, wifiInfo);
120                 break;
121             default: // NONE
122                 wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
123                 wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
124                 break;
125         }
126 
127         updateForProxy(
128                 wifiConf,
129                 wifiInfo.proxyHost,
130                 wifiInfo.proxyPort,
131                 wifiInfo.proxyBypassHosts,
132                 wifiInfo.pacUrl);
133         return wifiConf;
134     }
135 
maybeUpdateForEAPConfiguration(WifiConfiguration wifiConf, WifiInfo wifiInfo)136     private void maybeUpdateForEAPConfiguration(WifiConfiguration wifiConf, WifiInfo wifiInfo) {
137         try {
138             maybeUpdateForEAPConfigurationOrThrow(wifiConf, wifiInfo);
139         } catch (IOException | CertificateException | NoSuchAlgorithmException
140                 | UnrecoverableKeyException | KeyStoreException e) {
141             ProvisionLogger.loge("Error while reading certificate", e);
142         }
143     }
144 
maybeUpdateForEAPConfigurationOrThrow( WifiConfiguration wifiConf, WifiInfo wifiInfo)145     private void maybeUpdateForEAPConfigurationOrThrow(
146             WifiConfiguration wifiConf, WifiInfo wifiInfo)
147             throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException,
148             KeyStoreException, IOException {
149         if (!isEAPWifiInfoValid(wifiInfo.eapMethod)) {
150             ProvisionLogger.loge("Unknown EAP method: " + wifiInfo.eapMethod);
151             return;
152         }
153         if (!isPhase2AuthWifiInfoValid(wifiInfo.phase2Auth)) {
154             ProvisionLogger.loge(
155                     "Unknown phase 2 authentication method: " + wifiInfo.phase2Auth);
156             return;
157         }
158         wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X);
159         wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP);
160         WifiEnterpriseConfig wifiEnterpriseConfig = new WifiEnterpriseConfig();
161         updateWifiEnterpriseConfigFromWifiInfo(wifiEnterpriseConfig, wifiInfo);
162         maybeUpdateClientKeyForEAPConfiguration(wifiEnterpriseConfig, wifiInfo.userCertificate);
163         wifiConf.enterpriseConfig = wifiEnterpriseConfig;
164     }
165 
updateWifiEnterpriseConfigFromWifiInfo( WifiEnterpriseConfig wifiEnterpriseConfig, WifiInfo wifiInfo)166     private void updateWifiEnterpriseConfigFromWifiInfo(
167             WifiEnterpriseConfig wifiEnterpriseConfig, WifiInfo wifiInfo)
168             throws CertificateException, IOException {
169         wifiEnterpriseConfig.setEapMethod(getEAPMethodFromString(wifiInfo.eapMethod));
170         wifiEnterpriseConfig.setPhase2Method(getPhase2AuthFromString(wifiInfo.phase2Auth));
171         wifiEnterpriseConfig.setPassword(wifiInfo.password);
172         wifiEnterpriseConfig.setIdentity(wifiInfo.identity);
173         wifiEnterpriseConfig.setAnonymousIdentity(wifiInfo.anonymousIdentity);
174         wifiEnterpriseConfig.setDomainSuffixMatch(wifiInfo.domain);
175         if (!TextUtils.isEmpty(wifiInfo.caCertificate)) {
176             wifiEnterpriseConfig.setCaCertificate(buildCACertificate(wifiInfo.caCertificate));
177         }
178     }
179 
180     /**
181      * Updates client key information in EAP configuration if the key and certificate from {@code
182      * userCertificate} passes {@link #isKeyValidType(Key)} and {@link
183      * #isCertificateChainValidType(Certificate[])}.
184      */
maybeUpdateClientKeyForEAPConfiguration(WifiEnterpriseConfig wifiEnterpriseConfig, String userCertificate)185     private void maybeUpdateClientKeyForEAPConfiguration(WifiEnterpriseConfig wifiEnterpriseConfig,
186             String userCertificate)
187             throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException,
188             UnrecoverableKeyException {
189         if (TextUtils.isEmpty(userCertificate)) {
190             return;
191         }
192         KeyStore keyStore = loadKeystoreFromCertificate(userCertificate);
193         String alias = findAliasFromKeystore(keyStore);
194         if (TextUtils.isEmpty(alias) || !keyStore.isKeyEntry(alias)) {
195             return;
196         }
197         Key key = keyStore.getKey(alias, PASSWORD);
198         if (key == null) {
199             return;
200         }
201         if (!isKeyValidType(key)) {
202             ProvisionLogger.loge(
203                     "Key in user certificate must be non-null and PrivateKey type");
204             return;
205         }
206         Certificate[] certificates = keyStore.getCertificateChain(alias);
207         if (certificates == null) {
208             return;
209         }
210         if (!isCertificateChainValidType(certificates)) {
211             ProvisionLogger.loge(
212                     "All certificates in chain in user certificate must be non-null "
213                             + "X509Certificate type");
214             return;
215         }
216         wifiEnterpriseConfig.setClientKeyEntryWithCertificateChain(
217                 (PrivateKey) key, castX509Certificates(certificates));
218     }
219 
isCertificateChainValidType(Certificate[] certificates)220     private boolean isCertificateChainValidType(Certificate[] certificates) {
221         return !Arrays.stream(certificates).anyMatch(c -> !(c instanceof X509Certificate));
222     }
223 
isKeyValidType(Key key)224     private boolean isKeyValidType(Key key) {
225         return key instanceof PrivateKey;
226     }
227 
isPhase2AuthWifiInfoValid(String phase2Auth)228     private boolean isPhase2AuthWifiInfoValid(String phase2Auth) {
229         return PHASE2_AUTH.containsKey(phase2Auth);
230     }
231 
isEAPWifiInfoValid(String eapMethod)232     private boolean isEAPWifiInfoValid(String eapMethod) {
233         return EAP_METHODS.containsKey(eapMethod);
234     }
235 
updateForWPAConfiguration(WifiConfiguration wifiConf, String wifiPassword)236     private void updateForWPAConfiguration(WifiConfiguration wifiConf, String wifiPassword) {
237         wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
238         wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
239         wifiConf.allowedProtocols.set(WifiConfiguration.Protocol.WPA); // For WPA
240         wifiConf.allowedProtocols.set(WifiConfiguration.Protocol.RSN); // For WPA2
241         wifiConf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
242         wifiConf.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
243         wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
244         wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
245         if (!TextUtils.isEmpty(wifiPassword)) {
246             wifiConf.preSharedKey = "\"" + wifiPassword + "\"";
247         }
248     }
249 
updateForWEPConfiguration(WifiConfiguration wifiConf, String password)250     private void updateForWEPConfiguration(WifiConfiguration wifiConf, String password) {
251         wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
252         wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
253         wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
254         wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40);
255         wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104);
256         wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
257         wifiConf.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
258         int length = password.length();
259         if ((length == 10 || length == 26 || length == 58) && password.matches("[0-9A-Fa-f]*")) {
260             wifiConf.wepKeys[0] = password;
261         } else {
262             wifiConf.wepKeys[0] = '"' + password + '"';
263         }
264         wifiConf.wepTxKeyIndex = 0;
265     }
266 
267     /**
268      * Keystore must not contain more then one alias.
269      */
270     @Nullable
findAliasFromKeystore(KeyStore keyStore)271     private static String findAliasFromKeystore(KeyStore keyStore)
272             throws KeyStoreException, CertificateException {
273         List<String> aliases = Collections.list(keyStore.aliases());
274         if (aliases.isEmpty()) {
275             return null;
276         }
277         if (aliases.size() != 1) {
278             throw new CertificateException(
279                     "Configuration must contain only one certificate");
280         }
281         return aliases.get(0);
282     }
283 
loadKeystoreFromCertificate(String userCertificate)284     private static KeyStore loadKeystoreFromCertificate(String userCertificate)
285             throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
286         KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE_PKCS12);
287         try (InputStream inputStream = new ByteArrayInputStream(
288                 Base64.getDecoder().decode(userCertificate
289                         .getBytes(StandardCharsets.UTF_8)))) {
290             keyStore.load(inputStream, PASSWORD);
291         }
292         return keyStore;
293     }
294 
295     /**
296      * Casts the given certificate chain to a chain of {@link X509Certificate} objects. Assumes the
297      * given certificate chain passes {@link #isCertificateChainValidType(Certificate[])}.
298      */
castX509Certificates(Certificate[] certificateChain)299     private static X509Certificate[] castX509Certificates(Certificate[] certificateChain) {
300         return Arrays.stream(certificateChain)
301                 .map(certificate -> (X509Certificate) certificate)
302                 .toArray(X509Certificate[]::new);
303     }
304 
305     /**
306      * @param caCertificate String representation of CA certificate in the format described at
307      * {@link android.app.admin.DevicePolicyManager#EXTRA_PROVISIONING_WIFI_CA_CERTIFICATE}.
308      */
buildCACertificate(String caCertificate)309     private X509Certificate buildCACertificate(String caCertificate)
310             throws CertificateException, IOException {
311         CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
312         try (InputStream inputStream = new ByteArrayInputStream(Base64.getDecoder()
313                 .decode(caCertificate.getBytes(StandardCharsets.UTF_8)))) {
314         X509Certificate caCertificateX509 = (X509Certificate) certificateFactory
315                 .generateCertificate(inputStream);
316             return caCertificateX509;
317         }
318     }
319 
updateForProxy(WifiConfiguration wifiConf, String proxyHost, int proxyPort, String proxyBypassHosts, String pacUrl)320     private void updateForProxy(WifiConfiguration wifiConf, String proxyHost, int proxyPort,
321             String proxyBypassHosts, String pacUrl) {
322         if (TextUtils.isEmpty(proxyHost) && TextUtils.isEmpty(pacUrl)) {
323             return;
324         }
325         IpConfiguration ipConfig = wifiConf.getIpConfiguration();
326         if (!TextUtils.isEmpty(proxyHost)) {
327             ipConfig.setProxySettings(ProxySettings.STATIC);
328             ipConfig.setHttpProxy(new ProxyInfo(proxyHost, proxyPort, proxyBypassHosts));
329         } else {
330             ipConfig.setProxySettings(ProxySettings.PAC);
331             ipConfig.setHttpProxy(new ProxyInfo(Uri.parse(pacUrl)));
332         }
333         wifiConf.setIpConfiguration(ipConfig);
334     }
335 
getEAPMethodFromString(String eapMethod)336     private int getEAPMethodFromString(String eapMethod) {
337         if (EAP_METHODS.containsKey(eapMethod)) {
338             return EAP_METHODS.get(eapMethod);
339         }
340         throw new IllegalArgumentException("Unknown EAP method: " + eapMethod);
341     }
342 
getPhase2AuthFromString(String phase2Auth)343     private int getPhase2AuthFromString(String phase2Auth) {
344         if (PHASE2_AUTH.containsKey(phase2Auth)) {
345             return PHASE2_AUTH.get(phase2Auth);
346         }
347         throw new IllegalArgumentException("Unknown Phase 2 authentication method: " + phase2Auth);
348     }
349 }
350