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