1package com.android.server.wifi.configparse; 2 3import android.content.Context; 4import android.net.Uri; 5import android.net.wifi.WifiConfiguration; 6import android.net.wifi.WifiEnterpriseConfig; 7import android.telephony.SubscriptionManager; 8import android.telephony.TelephonyManager; 9import android.util.Base64; 10import android.util.Log; 11 12import com.android.server.wifi.IMSIParameter; 13import com.android.server.wifi.anqp.eap.AuthParam; 14import com.android.server.wifi.anqp.eap.EAP; 15import com.android.server.wifi.anqp.eap.EAPMethod; 16import com.android.server.wifi.anqp.eap.NonEAPInnerAuth; 17import com.android.server.wifi.hotspot2.omadm.MOManager; 18import com.android.server.wifi.hotspot2.pps.Credential; 19import com.android.server.wifi.hotspot2.pps.HomeSP; 20 21import org.xml.sax.SAXException; 22 23import java.io.ByteArrayInputStream; 24import java.io.IOException; 25import java.io.InputStreamReader; 26import java.io.LineNumberReader; 27import java.nio.charset.StandardCharsets; 28import java.security.GeneralSecurityException; 29import java.security.KeyStore; 30import java.security.MessageDigest; 31import java.security.PrivateKey; 32import java.security.cert.Certificate; 33import java.security.cert.CertificateFactory; 34import java.security.cert.X509Certificate; 35import java.util.ArrayList; 36import java.util.Arrays; 37import java.util.Enumeration; 38import java.util.HashSet; 39import java.util.List; 40 41public class ConfigBuilder { 42 public static final String WifiConfigType = "application/x-wifi-config"; 43 private static final String ProfileTag = "application/x-passpoint-profile"; 44 private static final String KeyTag = "application/x-pkcs12"; 45 private static final String CATag = "application/x-x509-ca-cert"; 46 47 private static final String X509 = "X.509"; 48 49 private static final String TAG = "WCFG"; 50 51 public static WifiConfiguration buildConfig(String uriString, byte[] data, Context context) 52 throws IOException, GeneralSecurityException, SAXException { 53 Log.d(TAG, "Content: " + (data != null ? data.length : -1)); 54 55 byte[] b64 = Base64.decode(new String(data, StandardCharsets.ISO_8859_1), Base64.DEFAULT); 56 Log.d(TAG, "Decoded: " + b64.length + " bytes."); 57 58 dropFile(Uri.parse(uriString), context); 59 60 MIMEContainer mimeContainer = new 61 MIMEContainer(new LineNumberReader( 62 new InputStreamReader(new ByteArrayInputStream(b64), StandardCharsets.ISO_8859_1)), 63 null); 64 if (!mimeContainer.isBase64()) { 65 throw new IOException("Encoding for " + 66 mimeContainer.getContentType() + " is not base64"); 67 } 68 MIMEContainer inner; 69 if (mimeContainer.getContentType().equals(WifiConfigType)) { 70 byte[] wrappedContent = Base64.decode(mimeContainer.getText(), Base64.DEFAULT); 71 Log.d(TAG, "Building container from '" + 72 new String(wrappedContent, StandardCharsets.ISO_8859_1) + "'"); 73 inner = new MIMEContainer(new LineNumberReader( 74 new InputStreamReader(new ByteArrayInputStream(wrappedContent), 75 StandardCharsets.ISO_8859_1)), null); 76 } 77 else { 78 inner = mimeContainer; 79 } 80 return parse(inner, context); 81 } 82 83 private static void dropFile(Uri uri, Context context) { 84 context.getContentResolver().delete(uri, null, null); 85 } 86 87 private static WifiConfiguration parse(MIMEContainer root, Context context) 88 throws IOException, GeneralSecurityException, SAXException { 89 90 if (root.getMimeContainers() == null) { 91 throw new IOException("Malformed MIME content: not multipart"); 92 } 93 94 String moText = null; 95 X509Certificate caCert = null; 96 PrivateKey clientKey = null; 97 List<X509Certificate> clientChain = null; 98 99 for (MIMEContainer subContainer : root.getMimeContainers()) { 100 Log.d(TAG, " + Content Type: " + subContainer.getContentType()); 101 switch (subContainer.getContentType()) { 102 case ProfileTag: 103 if (subContainer.isBase64()) { 104 byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT); 105 moText = new String(octets, StandardCharsets.UTF_8); 106 } else { 107 moText = subContainer.getText(); 108 } 109 Log.d(TAG, "OMA: " + moText); 110 break; 111 case CATag: { 112 if (!subContainer.isBase64()) { 113 throw new IOException("Can't read non base64 encoded cert"); 114 } 115 116 byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT); 117 CertificateFactory factory = CertificateFactory.getInstance(X509); 118 caCert = (X509Certificate) factory.generateCertificate( 119 new ByteArrayInputStream(octets)); 120 Log.d(TAG, "Cert subject " + caCert.getSubjectX500Principal()); 121 Log.d(TAG, "Full Cert: " + caCert); 122 break; 123 } 124 case KeyTag: { 125 if (!subContainer.isBase64()) { 126 throw new IOException("Can't read non base64 encoded key"); 127 } 128 129 byte[] octets = Base64.decode(subContainer.getText(), Base64.DEFAULT); 130 131 KeyStore ks = KeyStore.getInstance("PKCS12"); 132 ByteArrayInputStream in = new ByteArrayInputStream(octets); 133 ks.load(in, new char[0]); 134 in.close(); 135 Log.d(TAG, "---- Start PKCS12 info " + octets.length + ", size " + ks.size()); 136 Enumeration<String> aliases = ks.aliases(); 137 while (aliases.hasMoreElements()) { 138 String alias = aliases.nextElement(); 139 clientKey = (PrivateKey) ks.getKey(alias, null); 140 Log.d(TAG, "Key: " + clientKey.getFormat()); 141 Certificate[] chain = ks.getCertificateChain(alias); 142 if (chain != null) { 143 clientChain = new ArrayList<>(); 144 for (Certificate certificate : chain) { 145 if (!(certificate instanceof X509Certificate)) { 146 Log.w(TAG, "Element in cert chain is not an X509Certificate: " + 147 certificate.getClass()); 148 } 149 clientChain.add((X509Certificate) certificate); 150 } 151 Log.d(TAG, "Chain: " + clientChain.size()); 152 } 153 } 154 Log.d(TAG, "---- End PKCS12 info."); 155 break; 156 } 157 } 158 } 159 160 if (moText == null) { 161 throw new IOException("Missing profile"); 162 } 163 164 return buildConfig(moText, caCert, clientChain, clientKey, context); 165 } 166 167 private static WifiConfiguration buildConfig(String text, X509Certificate caCert, 168 List<X509Certificate> clientChain, PrivateKey key, 169 Context context) 170 throws IOException, SAXException, GeneralSecurityException { 171 172 HomeSP homeSP = MOManager.buildSP(text); 173 Credential credential = homeSP.getCredential(); 174 175 WifiConfiguration config; 176 177 EAP.EAPMethodID eapMethodID = credential.getEAPMethod().getEAPMethodID(); 178 switch (eapMethodID) { 179 case EAP_TTLS: 180 if (key != null || clientChain != null) { 181 Log.w(TAG, "Client cert and/or key included with EAP-TTLS profile"); 182 } 183 config = buildTTLSConfig(homeSP); 184 break; 185 case EAP_TLS: 186 config = buildTLSConfig(homeSP, clientChain, key); 187 break; 188 case EAP_AKA: 189 case EAP_AKAPrim: 190 case EAP_SIM: 191 if (key != null || clientChain != null || caCert != null) { 192 Log.i(TAG, "Client/CA cert and/or key included with " + 193 eapMethodID + " profile"); 194 } 195 config = buildSIMConfig(homeSP, context); 196 break; 197 default: 198 throw new IOException("Unsupported EAP Method: " + eapMethodID); 199 } 200 201 WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig; 202 203 enterpriseConfig.setCaCertificate(caCert); 204 enterpriseConfig.setAnonymousIdentity("anonymous@" + credential.getRealm()); 205 enterpriseConfig.setRealm(credential.getRealm()); 206 207 return config; 208 } 209 210 // Retain for debugging purposes 211 /* 212 private static void xIterateCerts(KeyStore ks, X509Certificate caCert) 213 throws GeneralSecurityException { 214 Enumeration<String> aliases = ks.aliases(); 215 while (aliases.hasMoreElements()) { 216 String alias = aliases.nextElement(); 217 Certificate cert = ks.getCertificate(alias); 218 Log.d("HS2J", "Checking " + alias); 219 if (cert instanceof X509Certificate) { 220 X509Certificate x509Certificate = (X509Certificate) cert; 221 boolean sm = x509Certificate.getSubjectX500Principal().equals( 222 caCert.getSubjectX500Principal()); 223 boolean eq = false; 224 if (sm) { 225 eq = Arrays.equals(x509Certificate.getEncoded(), caCert.getEncoded()); 226 } 227 Log.d("HS2J", "Subject: " + x509Certificate.getSubjectX500Principal() + 228 ": " + sm + "/" + eq); 229 } 230 } 231 } 232 */ 233 234 private static WifiConfiguration buildTTLSConfig(HomeSP homeSP) 235 throws IOException { 236 Credential credential = homeSP.getCredential(); 237 238 if (credential.getUserName() == null || credential.getPassword() == null) { 239 throw new IOException("EAP-TTLS provisioned without user name or password"); 240 } 241 242 EAPMethod eapMethod = credential.getEAPMethod(); 243 244 AuthParam authParam = eapMethod.getAuthParam(); 245 if (authParam == null || 246 authParam.getAuthInfoID() != EAP.AuthInfoID.NonEAPInnerAuthType) { 247 throw new IOException("Bad auth parameter for EAP-TTLS: " + authParam); 248 } 249 250 WifiConfiguration config = buildBaseConfiguration(homeSP); 251 NonEAPInnerAuth ttlsParam = (NonEAPInnerAuth) authParam; 252 WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig; 253 enterpriseConfig.setPhase2Method(remapInnerMethod(ttlsParam.getType())); 254 enterpriseConfig.setIdentity(credential.getUserName()); 255 enterpriseConfig.setPassword(credential.getPassword()); 256 257 return config; 258 } 259 260 private static WifiConfiguration buildTLSConfig(HomeSP homeSP, 261 List<X509Certificate> clientChain, 262 PrivateKey clientKey) 263 throws IOException, GeneralSecurityException { 264 265 Credential credential = homeSP.getCredential(); 266 267 X509Certificate clientCertificate = null; 268 269 if (clientKey == null || clientChain == null) { 270 throw new IOException("No key and/or cert passed for EAP-TLS"); 271 } 272 if (credential.getCertType() != Credential.CertType.x509v3) { 273 throw new IOException("Invalid certificate type for TLS: " + 274 credential.getCertType()); 275 } 276 277 byte[] reference = credential.getFingerPrint(); 278 MessageDigest digester = MessageDigest.getInstance("SHA-256"); 279 for (X509Certificate certificate : clientChain) { 280 digester.reset(); 281 byte[] fingerprint = digester.digest(certificate.getEncoded()); 282 if (Arrays.equals(reference, fingerprint)) { 283 clientCertificate = certificate; 284 break; 285 } 286 } 287 if (clientCertificate == null) { 288 throw new IOException("No certificate in chain matches supplied fingerprint"); 289 } 290 291 String alias = Base64.encodeToString(reference, Base64.DEFAULT); 292 293 WifiConfiguration config = buildBaseConfiguration(homeSP); 294 WifiEnterpriseConfig enterpriseConfig = config.enterpriseConfig; 295 enterpriseConfig.setClientCertificateAlias(alias); 296 enterpriseConfig.setClientKeyEntry(clientKey, clientCertificate); 297 298 return config; 299 } 300 301 private static WifiConfiguration buildSIMConfig(HomeSP homeSP, Context context) 302 throws IOException { 303 304 Credential credential = homeSP.getCredential(); 305 IMSIParameter credImsi = credential.getImsi(); 306 307 /* 308 * Uncomment to enforce strict IMSI matching with currently installed SIM cards. 309 * 310 TelephonyManager tm = TelephonyManager.from(context); 311 SubscriptionManager sub = SubscriptionManager.from(context); 312 boolean match = false; 313 314 for (int subId : sub.getActiveSubscriptionIdList()) { 315 String imsi = tm.getSubscriberId(subId); 316 if (credImsi.matches(imsi)) { 317 match = true; 318 break; 319 } 320 } 321 if (!match) { 322 throw new IOException("Supplied IMSI does not match any SIM card"); 323 } 324 */ 325 326 WifiConfiguration config = buildBaseConfiguration(homeSP); 327 config.enterpriseConfig.setPlmn(credImsi.toString()); 328 return config; 329 } 330 331 private static WifiConfiguration buildBaseConfiguration(HomeSP homeSP) throws IOException { 332 EAP.EAPMethodID eapMethodID = homeSP.getCredential().getEAPMethod().getEAPMethodID(); 333 334 WifiConfiguration config = new WifiConfiguration(); 335 336 config.FQDN = homeSP.getFQDN(); 337 338 HashSet<Long> roamingConsortiumIds = homeSP.getRoamingConsortiums(); 339 config.roamingConsortiumIds = new long[roamingConsortiumIds.size()]; 340 int i = 0; 341 for (long id : roamingConsortiumIds) { 342 config.roamingConsortiumIds[i] = id; 343 i++; 344 } 345 config.providerFriendlyName = homeSP.getFriendlyName(); 346 347 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP); 348 config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.IEEE8021X); 349 350 WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig(); 351 enterpriseConfig.setEapMethod(remapEAPMethod(eapMethodID)); 352 enterpriseConfig.setRealm(homeSP.getCredential().getRealm()); 353 config.enterpriseConfig = enterpriseConfig; 354 355 return config; 356 } 357 358 private static int remapEAPMethod(EAP.EAPMethodID eapMethodID) throws IOException { 359 switch (eapMethodID) { 360 case EAP_TTLS: 361 return WifiEnterpriseConfig.Eap.TTLS; 362 case EAP_TLS: 363 return WifiEnterpriseConfig.Eap.TLS; 364 case EAP_SIM: 365 return WifiEnterpriseConfig.Eap.SIM; 366 case EAP_AKA: 367 return WifiEnterpriseConfig.Eap.AKA; 368 case EAP_AKAPrim: 369 return WifiEnterpriseConfig.Eap.AKA_PRIME; 370 default: 371 throw new IOException("Bad EAP method: " + eapMethodID); 372 } 373 } 374 375 private static int remapInnerMethod(NonEAPInnerAuth.NonEAPType type) throws IOException { 376 switch (type) { 377 case PAP: 378 return WifiEnterpriseConfig.Phase2.PAP; 379 case MSCHAP: 380 return WifiEnterpriseConfig.Phase2.MSCHAP; 381 case MSCHAPv2: 382 return WifiEnterpriseConfig.Phase2.MSCHAPV2; 383 case CHAP: 384 default: 385 throw new IOException("Inner method " + type + " not supported"); 386 } 387 } 388} 389