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