• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 android.net.http;
18 
19 import com.android.org.conscrypt.Conscrypt;
20 import com.android.org.conscrypt.TrustManagerImpl;
21 
22 import android.util.Log;
23 
24 import java.io.ByteArrayInputStream;
25 import java.io.IOException;
26 import java.lang.reflect.InvocationTargetException;
27 import java.lang.reflect.Method;
28 import java.security.GeneralSecurityException;
29 import java.security.KeyStore;
30 import java.security.KeyStoreException;
31 import java.security.NoSuchAlgorithmException;
32 import java.security.cert.Certificate;
33 import java.security.cert.CertificateException;
34 import java.security.cert.CertificateFactory;
35 import java.security.cert.X509Certificate;
36 
37 import javax.net.ssl.HostnameVerifier;
38 import javax.net.ssl.HttpsURLConnection;
39 import javax.net.ssl.SSLHandshakeException;
40 import javax.net.ssl.SSLSession;
41 import javax.net.ssl.SSLSocket;
42 import javax.net.ssl.TrustManager;
43 import javax.net.ssl.TrustManagerFactory;
44 import javax.net.ssl.X509TrustManager;
45 
46 /**
47  * Class responsible for all server certificate validation functionality
48  */
49 public class CertificateChainValidator {
50     private static final String TAG = "CertificateChainValidator";
51 
52     private static class NoPreloadHolder {
53         /**
54          * The singleton instance of the certificate chain validator.
55          */
56         private static final CertificateChainValidator sInstance = new CertificateChainValidator();
57 
58         /**
59          * The singleton instance of the hostname verifier.
60          */
61         private static final HostnameVerifier sVerifier = HttpsURLConnection
62                 .getDefaultHostnameVerifier();
63     }
64 
65     private X509TrustManager mTrustManager;
66 
67     /**
68      * @return The singleton instance of the certificates chain validator
69      */
getInstance()70     public static CertificateChainValidator getInstance() {
71         return NoPreloadHolder.sInstance;
72     }
73 
74     /**
75      * Creates a new certificate chain validator. This is a private constructor.
76      * If you need a Certificate chain validator, call getInstance().
77      */
CertificateChainValidator()78     private CertificateChainValidator() {
79         try {
80             TrustManagerFactory tmf = TrustManagerFactory.getInstance("X.509");
81             tmf.init((KeyStore) null);
82             for (TrustManager tm : tmf.getTrustManagers()) {
83                 if (tm instanceof X509TrustManager) {
84                     mTrustManager = (X509TrustManager) tm;
85                 }
86             }
87         } catch (NoSuchAlgorithmException e) {
88             throw new RuntimeException("X.509 TrustManagerFactory must be available", e);
89         } catch (KeyStoreException e) {
90             throw new RuntimeException("X.509 TrustManagerFactory cannot be initialized", e);
91         }
92 
93         if (mTrustManager == null) {
94             throw new RuntimeException(
95                     "None of the X.509 TrustManagers are X509TrustManager");
96         }
97     }
98 
99     /**
100      * Performs the handshake and server certificates validation
101      * Notice a new chain will be rebuilt by tracing the issuer and subject
102      * before calling checkServerTrusted().
103      * And if the last traced certificate is self issued and it is expired, it
104      * will be dropped.
105      * @param sslSocket The secure connection socket
106      * @param domain The website domain
107      * @return An SSL error object if there is an error and null otherwise
108      */
doHandshakeAndValidateServerCertificates( HttpsConnection connection, SSLSocket sslSocket, String domain)109     public SslError doHandshakeAndValidateServerCertificates(
110             HttpsConnection connection, SSLSocket sslSocket, String domain)
111             throws IOException {
112         // get a valid SSLSession, close the socket if we fail
113         SSLSession sslSession = sslSocket.getSession();
114         if (!sslSession.isValid()) {
115             closeSocketThrowException(sslSocket, "failed to perform SSL handshake");
116         }
117 
118         // retrieve the chain of the server peer certificates
119         Certificate[] peerCertificates =
120             sslSocket.getSession().getPeerCertificates();
121 
122         if (peerCertificates == null || peerCertificates.length == 0) {
123             closeSocketThrowException(
124                 sslSocket, "failed to retrieve peer certificates");
125         } else {
126             // update the SSL certificate associated with the connection
127             if (connection != null) {
128                 if (peerCertificates[0] != null) {
129                     connection.setCertificate(
130                         new SslCertificate((X509Certificate)peerCertificates[0]));
131                 }
132             }
133         }
134 
135         return verifyServerDomainAndCertificates((X509Certificate[]) peerCertificates, domain, "RSA");
136     }
137 
138     /**
139      * Similar to doHandshakeAndValidateServerCertificates but exposed to JNI for use
140      * by Chromium HTTPS stack to validate the cert chain.
141      * @param certChain The bytes for certificates in ASN.1 DER encoded certificates format.
142      * @param domain The full website hostname and domain
143      * @param authType The authentication type for the cert chain
144      * @return An SSL error object if there is an error and null otherwise
145      */
verifyServerCertificates( byte[][] certChain, String domain, String authType)146     public static SslError verifyServerCertificates(
147         byte[][] certChain, String domain, String authType)
148         throws IOException {
149 
150         if (certChain == null || certChain.length == 0) {
151             throw new IllegalArgumentException("bad certificate chain");
152         }
153 
154         X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
155 
156         try {
157             CertificateFactory cf = CertificateFactory.getInstance("X.509");
158             for (int i = 0; i < certChain.length; ++i) {
159                 serverCertificates[i] = (X509Certificate) cf.generateCertificate(
160                         new ByteArrayInputStream(certChain[i]));
161             }
162         } catch (CertificateException e) {
163             throw new IOException("can't read certificate", e);
164         }
165 
166         return verifyServerDomainAndCertificates(serverCertificates, domain, authType);
167     }
168 
169     /**
170      * Handles updates to credential storage.
171      */
handleTrustStorageUpdate()172     public static void handleTrustStorageUpdate() {
173         TrustManagerFactory tmf;
174         try {
175             tmf = TrustManagerFactory.getInstance("X.509");
176             tmf.init((KeyStore) null);
177         } catch (NoSuchAlgorithmException e) {
178             Log.w(TAG, "Couldn't find default X.509 TrustManagerFactory");
179             return;
180         } catch (KeyStoreException e) {
181             Log.w(TAG, "Couldn't initialize default X.509 TrustManagerFactory", e);
182             return;
183         }
184 
185         TrustManager[] tms = tmf.getTrustManagers();
186         boolean sentUpdate = false;
187         for (TrustManager tm : tms) {
188             try {
189                 Method updateMethod = tm.getClass().getDeclaredMethod("handleTrustStorageUpdate");
190                 updateMethod.setAccessible(true);
191                 updateMethod.invoke(tm);
192                 sentUpdate = true;
193             } catch (Exception e) {
194             }
195         }
196         if (!sentUpdate) {
197             Log.w(TAG, "Didn't find a TrustManager to handle CA list update");
198         }
199     }
200 
201     /**
202      * Common code of doHandshakeAndValidateServerCertificates and verifyServerCertificates.
203      * Calls DomainNamevalidator to verify the domain, and TrustManager to verify the certs.
204      * @param chain the cert chain in X509 cert format.
205      * @param domain The full website hostname and domain
206      * @param authType The authentication type for the cert chain
207      * @return An SSL error object if there is an error and null otherwise
208      */
verifyServerDomainAndCertificates( X509Certificate[] chain, String domain, String authType)209     private static SslError verifyServerDomainAndCertificates(
210             X509Certificate[] chain, String domain, String authType)
211             throws IOException {
212         // check if the first certificate in the chain is for this site
213         X509Certificate currCertificate = chain[0];
214         if (currCertificate == null) {
215             throw new IllegalArgumentException("certificate for this site is null");
216         }
217 
218         boolean valid = domain != null
219                 && !domain.isEmpty()
220                 && NoPreloadHolder.sVerifier.verify(domain,
221                         new DelegatingSSLSession.CertificateWrap(currCertificate));
222         if (!valid) {
223             if (HttpLog.LOGV) {
224                 HttpLog.v("certificate not for this host: " + domain);
225             }
226             return new SslError(SslError.SSL_IDMISMATCH, currCertificate);
227         }
228 
229         try {
230             X509TrustManager x509TrustManager = Conscrypt.getDefaultX509TrustManager();
231             // Use duck-typing to try and call the hostname aware checkServerTrusted if
232             // available.
233             try {
234                 Method method = x509TrustManager.getClass().getMethod("checkServerTrusted",
235                         X509Certificate[].class,
236                         String.class,
237                         String.class);
238                 method.invoke(x509TrustManager, chain, authType, domain);
239             } catch (NoSuchMethodException | IllegalAccessException e) {
240                 x509TrustManager.checkServerTrusted(chain, authType);
241             } catch (InvocationTargetException e) {
242                 if (e.getCause() instanceof CertificateException) {
243                     throw (CertificateException) e.getCause();
244                 }
245                 throw new RuntimeException(e.getCause());
246             }
247             return null;  // No errors.
248         } catch (GeneralSecurityException e) {
249             if (HttpLog.LOGV) {
250                 HttpLog.v("failed to validate the certificate chain, error: " +
251                     e.getMessage());
252             }
253             return new SslError(SslError.SSL_UNTRUSTED, currCertificate);
254         }
255     }
256 
257     /**
258      * Returns the platform default {@link X509TrustManager}.
259      */
getTrustManager()260     private X509TrustManager getTrustManager() {
261         return mTrustManager;
262     }
263 
closeSocketThrowException( SSLSocket socket, String errorMessage, String defaultErrorMessage)264     private void closeSocketThrowException(
265             SSLSocket socket, String errorMessage, String defaultErrorMessage)
266             throws IOException {
267         closeSocketThrowException(
268             socket, errorMessage != null ? errorMessage : defaultErrorMessage);
269     }
270 
closeSocketThrowException(SSLSocket socket, String errorMessage)271     private void closeSocketThrowException(SSLSocket socket,
272             String errorMessage) throws IOException {
273         if (HttpLog.LOGV) {
274             HttpLog.v("validation error: " + errorMessage);
275         }
276 
277         if (socket != null) {
278             SSLSession session = socket.getSession();
279             if (session != null) {
280                 session.invalidate();
281             }
282 
283             socket.close();
284         }
285 
286         throw new SSLHandshakeException(errorMessage);
287     }
288 }
289