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 }