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