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