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