1 /* 2 * Copyright 2017 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.server.wifi.hotspot2; 18 19 import android.annotation.NonNull; 20 import android.net.Network; 21 import android.os.Handler; 22 import android.os.HandlerThread; 23 import android.os.Looper; 24 import android.text.TextUtils; 25 import android.util.Log; 26 import android.util.Pair; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.org.conscrypt.TrustManagerImpl; 30 import com.android.server.wifi.hotspot2.soap.HttpsServiceConnection; 31 import com.android.server.wifi.hotspot2.soap.HttpsTransport; 32 import com.android.server.wifi.hotspot2.soap.SoapParser; 33 import com.android.server.wifi.hotspot2.soap.SppResponseMessage; 34 35 import org.ksoap2.HeaderProperty; 36 import org.ksoap2.serialization.AttributeInfo; 37 import org.ksoap2.serialization.SoapObject; 38 import org.ksoap2.serialization.SoapSerializationEnvelope; 39 40 import java.io.ByteArrayInputStream; 41 import java.io.ByteArrayOutputStream; 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.net.HttpURLConnection; 45 import java.net.URL; 46 import java.net.URLConnection; 47 import java.security.KeyManagementException; 48 import java.security.cert.CertificateException; 49 import java.security.cert.CertificateFactory; 50 import java.security.cert.CertificateParsingException; 51 import java.security.cert.X509Certificate; 52 import java.util.ArrayList; 53 import java.util.Collection; 54 import java.util.HashMap; 55 import java.util.List; 56 import java.util.Locale; 57 import java.util.Map; 58 59 import javax.net.ssl.HttpsURLConnection; 60 import javax.net.ssl.SSLContext; 61 import javax.net.ssl.SSLHandshakeException; 62 import javax.net.ssl.SSLSocket; 63 import javax.net.ssl.SSLSocketFactory; 64 import javax.net.ssl.TrustManager; 65 import javax.net.ssl.X509TrustManager; 66 67 /** 68 * Provides methods to interface with the OSU server 69 */ 70 public class OsuServerConnection { 71 private static final String TAG = "PasspointOsuServerConnection"; 72 73 private static final int DNS_NAME = 2; 74 75 private SSLSocketFactory mSocketFactory; 76 private URL mUrl; 77 private Network mNetwork; 78 private WFATrustManager mTrustManager; 79 private HttpsTransport mHttpsTransport; 80 private HttpsServiceConnection mServiceConnection = null; 81 private HttpsURLConnection mUrlConnection = null; 82 private HandlerThread mOsuServerHandlerThread; 83 private Handler mHandler; 84 private PasspointProvisioner.OsuServerCallbacks mOsuServerCallbacks; 85 private boolean mSetupComplete = false; 86 private boolean mVerboseLoggingEnabled = false; 87 private Looper mLooper; 88 89 public static final int TRUST_CERT_TYPE_AAA = 1; 90 public static final int TRUST_CERT_TYPE_REMEDIATION = 2; 91 public static final int TRUST_CERT_TYPE_POLICY = 3; 92 93 @VisibleForTesting OsuServerConnection(Looper looper)94 /* package */ OsuServerConnection(Looper looper) { 95 mLooper = looper; 96 } 97 98 /** 99 * Sets up callback for event 100 * 101 * @param callbacks OsuServerCallbacks to be invoked for server related events 102 */ setEventCallback(PasspointProvisioner.OsuServerCallbacks callbacks)103 public void setEventCallback(PasspointProvisioner.OsuServerCallbacks callbacks) { 104 mOsuServerCallbacks = callbacks; 105 } 106 107 /** 108 * Initializes socket factory for server connection using HTTPS 109 * 110 * @param tlsContext SSLContext that will be used for HTTPS connection 111 * @param trustManagerImpl TrustManagerImpl delegate to validate certs 112 */ init(SSLContext tlsContext, TrustManagerImpl trustManagerImpl)113 public void init(SSLContext tlsContext, TrustManagerImpl trustManagerImpl) { 114 if (tlsContext == null) { 115 return; 116 } 117 try { 118 mTrustManager = new WFATrustManager(trustManagerImpl); 119 tlsContext.init(null, new TrustManager[]{mTrustManager}, null); 120 mSocketFactory = tlsContext.getSocketFactory(); 121 } catch (KeyManagementException e) { 122 Log.w(TAG, "Initialization failed"); 123 e.printStackTrace(); 124 return; 125 } 126 mSetupComplete = true; 127 128 // If mLooper is already set by unit test, don't overwrite it. 129 if (mLooper == null) { 130 mOsuServerHandlerThread = new HandlerThread("OsuServerHandler"); 131 mOsuServerHandlerThread.start(); 132 mLooper = mOsuServerHandlerThread.getLooper(); 133 } 134 mHandler = new Handler(mLooper); 135 } 136 137 /** 138 * Provides the capability to run OSU server validation 139 * 140 * @return boolean true if capability available 141 */ canValidateServer()142 public boolean canValidateServer() { 143 return mSetupComplete; 144 } 145 146 /** 147 * Enables verbose logging 148 * 149 * @param verbose a value greater than zero enables verbose logging 150 */ enableVerboseLogging(int verbose)151 public void enableVerboseLogging(int verbose) { 152 mVerboseLoggingEnabled = verbose > 0 ? true : false; 153 } 154 155 /** 156 * Connects to the OSU server 157 * 158 * @param url Osu Server's URL 159 * @param network current network connection 160 * @return {@code true} if {@code url} and {@code network} are not null 161 * 162 * Note: Relies on the caller to ensure that the capability to validate the OSU 163 * Server is available. 164 */ connect(@onNull URL url, @NonNull Network network)165 public boolean connect(@NonNull URL url, @NonNull Network network) { 166 if (url == null) { 167 Log.e(TAG, "URL is null"); 168 return false; 169 } 170 if (network == null) { 171 Log.e(TAG, "network is null"); 172 return false; 173 } 174 175 String protocol = url.getProtocol(); 176 // According to section 7.5.1 OSU operational requirements, in HS2.0 R3 specification, 177 // the URL must be HTTPS. Enforce it here. 178 if (!TextUtils.equals(protocol, "https")) { 179 Log.e(TAG, "OSU server URL must be HTTPS"); 180 return false; 181 } 182 183 mHandler.post(() -> performTlsConnection(url, network)); 184 return true; 185 } 186 187 /** 188 * Validates the service provider by comparing its identities found in OSU Server cert 189 * to the friendlyName obtained from ANQP exchange that is displayed to the user. 190 * 191 * @param friendlyNames the friendly names used for finding the same name in 192 * subjectAltName section of the certificate, which is a map of language 193 * codes from ISO-639 and names. 194 * @return boolean true if friendlyName shows up as one of the identities in the cert 195 */ validateProvider( Map<String, String> friendlyNames)196 public boolean validateProvider( 197 Map<String, String> friendlyNames) { 198 199 if (friendlyNames.size() == 0) { 200 return false; 201 } 202 203 for (Pair<Locale, String> identity : ServiceProviderVerifier.getProviderNames( 204 mTrustManager.getProviderCert())) { 205 if (identity.first == null || TextUtils.isEmpty(identity.second)) continue; 206 207 // Compare the language code for ISO-639. 208 if (TextUtils.equals(identity.second, 209 friendlyNames.get(identity.first.getISO3Language()))) { 210 if (mVerboseLoggingEnabled) { 211 Log.v(TAG, "OSU certificate is valid for " 212 + identity.first.getISO3Language() + "/" + identity.second); 213 } 214 return true; 215 } 216 } 217 return false; 218 } 219 220 /** 221 * The helper method to exchange a SOAP message. 222 * 223 * @param soapEnvelope the soap message to be sent. 224 * @return {@code true} if {@link Network} is valid and {@code soapEnvelope} is not {@code 225 * null}, {@code false} otherwise. 226 */ exchangeSoapMessage(@onNull SoapSerializationEnvelope soapEnvelope)227 public boolean exchangeSoapMessage(@NonNull SoapSerializationEnvelope soapEnvelope) { 228 if (mNetwork == null) { 229 Log.e(TAG, "Network is not established"); 230 return false; 231 } 232 233 if (mUrlConnection == null) { 234 Log.e(TAG, "Server certificate is not validated"); 235 return false; 236 } 237 238 if (soapEnvelope == null) { 239 Log.e(TAG, "soapEnvelope is null"); 240 return false; 241 } 242 243 mHandler.post(() -> performSoapMessageExchange(soapEnvelope)); 244 return true; 245 } 246 247 /** 248 * Retrieves Trust Root CA certificates for AAA, Remediation, Policy Server 249 * 250 * @param trustCertsInfo trust cert information for each type (AAA,Remediation and Policy). 251 * {@code Key} is the cert type. 252 * {@code Value} is the map that has a key for certUrl and a value for 253 * fingerprint of the certificate. 254 * @return {@code true} if {@link Network} is valid and {@code trustCertsInfo} is not {@code 255 * null}, {@code false} otherwise. 256 */ retrieveTrustRootCerts( @onNull Map<Integer, Map<String, byte[]>> trustCertsInfo)257 public boolean retrieveTrustRootCerts( 258 @NonNull Map<Integer, Map<String, byte[]>> trustCertsInfo) { 259 if (mNetwork == null) { 260 Log.e(TAG, "Network is not established"); 261 return false; 262 } 263 264 if (mUrlConnection == null) { 265 Log.e(TAG, "Server certificate is not validated"); 266 return false; 267 } 268 269 if (trustCertsInfo == null || trustCertsInfo.isEmpty()) { 270 Log.e(TAG, "TrustCertsInfo is not valid"); 271 return false; 272 } 273 mHandler.post(() -> performRetrievingTrustRootCerts(trustCertsInfo)); 274 return true; 275 } 276 performTlsConnection(URL url, Network network)277 private void performTlsConnection(URL url, Network network) { 278 mNetwork = network; 279 mUrl = url; 280 281 URLConnection urlConnection; 282 HttpsURLConnection httpsURLConnection; 283 284 try { 285 urlConnection = mNetwork.openConnection(mUrl); 286 } catch (IOException e) { 287 Log.e(TAG, "Unable to establish a URL connection: " + e); 288 if (mOsuServerCallbacks != null) { 289 mOsuServerCallbacks.onServerConnectionStatus( 290 mOsuServerCallbacks.getSessionId(), 291 false); 292 } 293 return; 294 } 295 296 if (urlConnection instanceof HttpsURLConnection) { 297 httpsURLConnection = (HttpsURLConnection) urlConnection; 298 } else { 299 Log.e(TAG, "Invalid URL connection"); 300 if (mOsuServerCallbacks != null) { 301 mOsuServerCallbacks.onServerConnectionStatus(mOsuServerCallbacks.getSessionId(), 302 false); 303 } 304 return; 305 } 306 307 try { 308 httpsURLConnection.setSSLSocketFactory(mSocketFactory); 309 httpsURLConnection.setConnectTimeout(HttpsServiceConnection.DEFAULT_TIMEOUT_MS); 310 httpsURLConnection.setReadTimeout(HttpsServiceConnection.DEFAULT_TIMEOUT_MS); 311 httpsURLConnection.connect(); 312 } catch (IOException e) { 313 Log.e(TAG, "Unable to establish a URL connection: " + e); 314 if (mOsuServerCallbacks != null) { 315 mOsuServerCallbacks.onServerConnectionStatus(mOsuServerCallbacks.getSessionId(), 316 false); 317 } 318 return; 319 } 320 mUrlConnection = httpsURLConnection; 321 if (mOsuServerCallbacks != null) { 322 mOsuServerCallbacks.onServerConnectionStatus(mOsuServerCallbacks.getSessionId(), true); 323 } 324 } 325 performSoapMessageExchange(@onNull SoapSerializationEnvelope soapEnvelope)326 private void performSoapMessageExchange(@NonNull SoapSerializationEnvelope soapEnvelope) { 327 if (mServiceConnection != null) { 328 mServiceConnection.disconnect(); 329 } 330 331 mServiceConnection = getServiceConnection(mUrl, mNetwork); 332 if (mServiceConnection == null) { 333 Log.e(TAG, "ServiceConnection for https is null"); 334 if (mOsuServerCallbacks != null) { 335 mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), null); 336 } 337 return; 338 } 339 340 SppResponseMessage sppResponse; 341 try { 342 // Sending the SOAP message 343 mHttpsTransport.call("", soapEnvelope); 344 Object response = soapEnvelope.bodyIn; 345 if (response == null) { 346 Log.e(TAG, "SoapObject is null"); 347 if (mOsuServerCallbacks != null) { 348 mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), 349 null); 350 } 351 return; 352 } 353 if (!(response instanceof SoapObject)) { 354 Log.e(TAG, "Not a SoapObject instance"); 355 if (mOsuServerCallbacks != null) { 356 mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), 357 null); 358 } 359 return; 360 } 361 SoapObject soapResponse = (SoapObject) response; 362 if (mVerboseLoggingEnabled) { 363 for (int i = 0; i < soapResponse.getAttributeCount(); i++) { 364 AttributeInfo attributeInfo = new AttributeInfo(); 365 soapResponse.getAttributeInfo(i, attributeInfo); 366 Log.v(TAG, "Attribute : " + attributeInfo.toString()); 367 } 368 Log.v(TAG, "response : " + soapResponse.toString()); 369 } 370 371 // Get the parsed SOAP SPP Response message 372 sppResponse = SoapParser.getResponse(soapResponse); 373 } catch (Exception e) { 374 if (e instanceof SSLHandshakeException) { 375 Log.e(TAG, "Failed to make TLS connection: " + e); 376 } else { 377 Log.e(TAG, "Failed to exchange the SOAP message: " + e); 378 } 379 if (mOsuServerCallbacks != null) { 380 mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), null); 381 } 382 return; 383 } finally { 384 mServiceConnection.disconnect(); 385 mServiceConnection = null; 386 } 387 if (mOsuServerCallbacks != null) { 388 mOsuServerCallbacks.onReceivedSoapMessage(mOsuServerCallbacks.getSessionId(), 389 sppResponse); 390 } 391 } 392 performRetrievingTrustRootCerts( @onNull Map<Integer, Map<String, byte[]>> trustCertsInfo)393 private void performRetrievingTrustRootCerts( 394 @NonNull Map<Integer, Map<String, byte[]>> trustCertsInfo) { 395 // Key: CERT_TYPE (AAA, REMEDIATION, POLICY), Value: a list of X509Certificate retrieved for 396 // the type. 397 Map<Integer, List<X509Certificate>> trustRootCertificates = new HashMap<>(); 398 399 for (Map.Entry<Integer, Map<String, byte[]>> certInfoPerType : trustCertsInfo.entrySet()) { 400 List<X509Certificate> certificates = new ArrayList<>(); 401 402 // Iterates certInfo to get a cert with a url provided in certInfo.key(). 403 // Key: Cert url, Value: SHA-256 hash bytes to match the fingerprint of a 404 // certificates retrieved from server. 405 for (Map.Entry<String, byte[]> certInfo : certInfoPerType.getValue().entrySet()) { 406 if (certInfo.getValue() == null) { 407 // clear all of retrieved CA certs so that PasspointProvisioner aborts 408 // current flow. 409 trustRootCertificates.clear(); 410 break; 411 } 412 X509Certificate certificate = getCert(certInfo.getKey()); 413 414 if (certificate == null || !ServiceProviderVerifier.verifyCertFingerprint( 415 certificate, certInfo.getValue())) { 416 // If any failure happens, clear all of retrieved CA certs so that 417 // PasspointProvisioner aborts current flow. 418 trustRootCertificates.clear(); 419 break; 420 } 421 certificates.add(certificate); 422 } 423 if (!certificates.isEmpty()) { 424 trustRootCertificates.put(certInfoPerType.getKey(), certificates); 425 } 426 } 427 428 if (mOsuServerCallbacks != null) { 429 // If it passes empty trustRootCertificates here, PasspointProvisioner will abort 430 // current flow because it indicates that client device doesn't get any trust root 431 // certificates from server. 432 mOsuServerCallbacks.onReceivedTrustRootCertificates(mOsuServerCallbacks.getSessionId(), 433 trustRootCertificates); 434 } 435 } 436 437 /** 438 * Retrieves a X.509 Certificate from server. 439 * 440 * @param certUrl url to retrieve a X.509 Certificate 441 * @return {@link X509Certificate} in success, {@code null} otherwise. 442 */ getCert(@onNull String certUrl)443 private X509Certificate getCert(@NonNull String certUrl) { 444 if (certUrl == null || !certUrl.toLowerCase(Locale.US).startsWith("https://")) { 445 Log.e(TAG, "invalid certUrl provided"); 446 return null; 447 } 448 449 try { 450 URL serverUrl = new URL(certUrl); 451 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 452 if (mServiceConnection != null) { 453 mServiceConnection.disconnect(); 454 } 455 mServiceConnection = getServiceConnection(serverUrl, mNetwork); 456 if (mServiceConnection == null) { 457 return null; 458 } 459 mServiceConnection.setRequestMethod("GET"); 460 mServiceConnection.setRequestProperty("Accept-Encoding", "gzip"); 461 462 if (mServiceConnection.getResponseCode() != HttpURLConnection.HTTP_OK) { 463 Log.e(TAG, "The response code of the HTTPS GET to " + certUrl 464 + " is not OK, but " + mServiceConnection.getResponseCode()); 465 return null; 466 } 467 boolean bPkcs7 = false; 468 boolean bBase64 = false; 469 List<HeaderProperty> properties = mServiceConnection.getResponseProperties(); 470 for (HeaderProperty property : properties) { 471 if (property == null || property.getKey() == null || property.getValue() == null) { 472 continue; 473 } 474 if (property.getKey().equalsIgnoreCase("Content-Type")) { 475 if (property.getValue().equals("application/pkcs7-mime") 476 || property.getValue().equals("application/x-x509-ca-cert")) { 477 // application/x-x509-ca-cert : File content is a DER encoded X.509 478 // certificate 479 if (mVerboseLoggingEnabled) { 480 Log.v(TAG, "a certificate found in a HTTPS response from " + certUrl); 481 } 482 483 // ca cert 484 bPkcs7 = true; 485 } 486 } 487 if (property.getKey().equalsIgnoreCase("Content-Transfer-Encoding") 488 && property.getValue().equalsIgnoreCase("base64")) { 489 if (mVerboseLoggingEnabled) { 490 Log.v(TAG, 491 "base64 encoding content in a HTTP response from " + certUrl); 492 } 493 bBase64 = true; 494 } 495 } 496 if (!bPkcs7) { 497 Log.e(TAG, "no X509Certificate found in the HTTPS response"); 498 return null; 499 } 500 InputStream in = mServiceConnection.openInputStream(); 501 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 502 byte[] buf = new byte[8192]; 503 while (true) { 504 int rd = in.read(buf, 0, 8192); 505 if (rd == -1) { 506 break; 507 } 508 bos.write(buf, 0, rd); 509 } 510 in.close(); 511 bos.flush(); 512 byte[] byteArray = bos.toByteArray(); 513 if (bBase64) { 514 String s = new String(byteArray); 515 byteArray = android.util.Base64.decode(s, android.util.Base64.DEFAULT); 516 } 517 518 X509Certificate certificate = (X509Certificate) certFactory.generateCertificate( 519 new ByteArrayInputStream(byteArray)); 520 if (mVerboseLoggingEnabled) { 521 Log.v(TAG, "cert : " + certificate.getSubjectDN()); 522 } 523 return certificate; 524 } catch (IOException e) { 525 Log.e(TAG, "Failed to get the data from " + certUrl + ": " + e); 526 } catch (CertificateException e) { 527 Log.e(TAG, "Failed to get instance for CertificateFactory " + e); 528 } catch (IllegalArgumentException e) { 529 Log.e(TAG, "Failed to decode the data: " + e); 530 } finally { 531 mServiceConnection.disconnect(); 532 mServiceConnection = null; 533 } 534 return null; 535 } 536 537 /** 538 * Gets the HTTPS service connection used for SOAP message exchange. 539 * 540 * @return {@link HttpsServiceConnection} 541 */ getServiceConnection(@onNull URL url, @NonNull Network network)542 private HttpsServiceConnection getServiceConnection(@NonNull URL url, 543 @NonNull Network network) { 544 HttpsServiceConnection serviceConnection; 545 try { 546 // Creates new HTTPS connection. 547 mHttpsTransport = HttpsTransport.createInstance(network, url); 548 serviceConnection = (HttpsServiceConnection) mHttpsTransport.getServiceConnection(); 549 if (serviceConnection != null) { 550 serviceConnection.setSSLSocketFactory(mSocketFactory); 551 } 552 } catch (IOException e) { 553 Log.e(TAG, "Unable to establish a URL connection"); 554 return null; 555 } 556 return serviceConnection; 557 } 558 cleanupConnection()559 private void cleanupConnection() { 560 if (mUrlConnection != null) { 561 mUrlConnection.disconnect(); 562 mUrlConnection = null; 563 } 564 if (mServiceConnection != null) { 565 mServiceConnection.disconnect(); 566 mServiceConnection = null; 567 } 568 } 569 570 /** 571 * Cleans up 572 */ cleanup()573 public void cleanup() { 574 mHandler.post(() -> cleanupConnection()); 575 } 576 577 private class WFATrustManager implements X509TrustManager { 578 private TrustManagerImpl mDelegate; 579 private List<X509Certificate> mServerCerts; 580 WFATrustManager(TrustManagerImpl trustManagerImpl)581 WFATrustManager(TrustManagerImpl trustManagerImpl) { 582 mDelegate = trustManagerImpl; 583 } 584 585 @Override checkClientTrusted(X509Certificate[] chain, String authType)586 public void checkClientTrusted(X509Certificate[] chain, String authType) 587 throws CertificateException { 588 if (mVerboseLoggingEnabled) { 589 Log.v(TAG, "checkClientTrusted " + authType); 590 } 591 } 592 593 @Override checkServerTrusted(X509Certificate[] chain, String authType)594 public void checkServerTrusted(X509Certificate[] chain, String authType) 595 throws CertificateException { 596 if (mVerboseLoggingEnabled) { 597 Log.v(TAG, "checkServerTrusted " + authType); 598 } 599 boolean certsValid = false; 600 try { 601 // Perform certificate path validation and get validated certs 602 mServerCerts = mDelegate.getTrustedChainForServer(chain, authType, 603 (SSLSocket) null); 604 certsValid = true; 605 } catch (CertificateException e) { 606 Log.e(TAG, "Certificate validation failure: " + e); 607 int i = 0; 608 for (X509Certificate cert : chain) { 609 // Provide some more details about the invalid certificate 610 Log.e(TAG, "Cert " + i + " details: " + cert.getSubjectDN()); 611 Log.e(TAG, "Not before: " + cert.getNotBefore() + ", not after: " 612 + cert.getNotAfter()); 613 Log.e(TAG, "Cert " + i + " issuer: " + cert.getIssuerDN()); 614 i++; 615 } 616 } 617 if (mOsuServerCallbacks != null) { 618 mOsuServerCallbacks.onServerValidationStatus(mOsuServerCallbacks.getSessionId(), 619 certsValid); 620 } 621 } 622 623 @Override getAcceptedIssuers()624 public X509Certificate[] getAcceptedIssuers() { 625 if (mVerboseLoggingEnabled) { 626 Log.v(TAG, "getAcceptedIssuers "); 627 } 628 return null; 629 } 630 631 /** 632 * Returns the OSU certificate matching the FQDN of the OSU server 633 * 634 * @return {@link X509Certificate} OSU certificate matching FQDN of OSU server 635 */ getProviderCert()636 public X509Certificate getProviderCert() { 637 if (mServerCerts == null || mServerCerts.size() <= 0) { 638 return null; 639 } 640 X509Certificate providerCert = null; 641 String fqdn = mUrl.getHost(); 642 try { 643 for (X509Certificate certificate : mServerCerts) { 644 Collection<List<?>> col = certificate.getSubjectAlternativeNames(); 645 if (col == null) { 646 continue; 647 } 648 for (List<?> name : col) { 649 if (name == null) { 650 continue; 651 } 652 if (name.size() >= DNS_NAME 653 && name.get(0).getClass() == Integer.class 654 && name.get(1).toString().equals(fqdn)) { 655 providerCert = certificate; 656 if (mVerboseLoggingEnabled) { 657 Log.v(TAG, "OsuCert found"); 658 } 659 break; 660 } 661 } 662 } 663 } catch (CertificateParsingException e) { 664 Log.e(TAG, "Unable to match certificate to " + fqdn); 665 if (mVerboseLoggingEnabled) { 666 e.printStackTrace(); 667 } 668 } 669 return providerCert; 670 } 671 } 672 } 673 674