• Home
  • 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 org.apache.harmony.xnet.provider.jsse.SSLParameters;
20 
21 import java.io.IOException;
22 
23 import java.security.cert.Certificate;
24 import java.security.cert.CertificateException;
25 import java.security.cert.CertificateExpiredException;
26 import java.security.cert.CertificateNotYetValidException;
27 import java.security.cert.X509Certificate;
28 import java.security.GeneralSecurityException;
29 import java.security.KeyStore;
30 
31 import javax.net.ssl.SSLHandshakeException;
32 import javax.net.ssl.SSLSession;
33 import javax.net.ssl.SSLSocket;
34 import javax.net.ssl.TrustManager;
35 import javax.net.ssl.TrustManagerFactory;
36 import javax.net.ssl.X509TrustManager;
37 
38 /**
39  * Class responsible for all server certificate validation functionality
40  *
41  * {@hide}
42  */
43 class CertificateChainValidator {
44 
45     /**
46      * The singleton instance of the certificate chain validator
47      */
48     private static final CertificateChainValidator sInstance
49             = new CertificateChainValidator();
50 
51     /**
52      * @return The singleton instance of the certificator chain validator
53      */
getInstance()54     public static CertificateChainValidator getInstance() {
55         return sInstance;
56     }
57 
58     /**
59      * Creates a new certificate chain validator. This is a pivate constructor.
60      * If you need a Certificate chain validator, call getInstance().
61      */
CertificateChainValidator()62     private CertificateChainValidator() {}
63 
64     /**
65      * Performs the handshake and server certificates validation
66      * @param sslSocket The secure connection socket
67      * @param domain The website domain
68      * @return An SSL error object if there is an error and null otherwise
69      */
doHandshakeAndValidateServerCertificates( HttpsConnection connection, SSLSocket sslSocket, String domain)70     public SslError doHandshakeAndValidateServerCertificates(
71             HttpsConnection connection, SSLSocket sslSocket, String domain)
72             throws IOException {
73         X509Certificate[] serverCertificates = null;
74 
75         // start handshake, close the socket if we fail
76         try {
77             sslSocket.setUseClientMode(true);
78             sslSocket.startHandshake();
79         } catch (IOException e) {
80             closeSocketThrowException(
81                 sslSocket, e.getMessage(),
82                 "failed to perform SSL handshake");
83         }
84 
85         // retrieve the chain of the server peer certificates
86         Certificate[] peerCertificates =
87             sslSocket.getSession().getPeerCertificates();
88 
89         if (peerCertificates == null || peerCertificates.length <= 0) {
90             closeSocketThrowException(
91                 sslSocket, "failed to retrieve peer certificates");
92         } else {
93             serverCertificates =
94                 new X509Certificate[peerCertificates.length];
95             for (int i = 0; i < peerCertificates.length; ++i) {
96                 serverCertificates[i] =
97                     (X509Certificate)(peerCertificates[i]);
98             }
99 
100             // update the SSL certificate associated with the connection
101             if (connection != null) {
102                 if (serverCertificates[0] != null) {
103                     connection.setCertificate(
104                         new SslCertificate(serverCertificates[0]));
105                 }
106             }
107         }
108 
109         // check if the first certificate in the chain is for this site
110         X509Certificate currCertificate = serverCertificates[0];
111         if (currCertificate == null) {
112             closeSocketThrowException(
113                 sslSocket, "certificate for this site is null");
114         } else {
115             if (!DomainNameChecker.match(currCertificate, domain)) {
116                 String errorMessage = "certificate not for this host: " + domain;
117 
118                 if (HttpLog.LOGV) {
119                     HttpLog.v(errorMessage);
120                 }
121 
122                 sslSocket.getSession().invalidate();
123                 return new SslError(
124                     SslError.SSL_IDMISMATCH, currCertificate);
125             }
126         }
127 
128         // first, we validate the chain using the standard validation
129         // solution; if we do not find any errors, we are done; if we
130         // fail the standard validation, we re-validate again below,
131         // this time trying to retrieve any individual errors we can
132         // report back to the user.
133         //
134         try {
135             SSLParameters.getDefaultTrustManager().checkServerTrusted(
136                 serverCertificates, "RSA");
137 
138             // no errors!!!
139             return null;
140         } catch (CertificateException e) {
141             if (HttpLog.LOGV) {
142                 HttpLog.v(
143                     "failed to pre-validate the certificate chain, error: " +
144                     e.getMessage());
145             }
146         }
147 
148         sslSocket.getSession().invalidate();
149 
150         SslError error = null;
151 
152         // we check the root certificate separately from the rest of the
153         // chain; this is because we need to know what certificate in
154         // the chain resulted in an error if any
155         currCertificate =
156             serverCertificates[serverCertificates.length - 1];
157         if (currCertificate == null) {
158             closeSocketThrowException(
159                 sslSocket, "root certificate is null");
160         }
161 
162         // check if the last certificate in the chain (root) is trusted
163         X509Certificate[] rootCertificateChain = { currCertificate };
164         try {
165             SSLParameters.getDefaultTrustManager().checkServerTrusted(
166                 rootCertificateChain, "RSA");
167         } catch (CertificateExpiredException e) {
168             String errorMessage = e.getMessage();
169             if (errorMessage == null) {
170                 errorMessage = "root certificate has expired";
171             }
172 
173             if (HttpLog.LOGV) {
174                 HttpLog.v(errorMessage);
175             }
176 
177             error = new SslError(
178                 SslError.SSL_EXPIRED, currCertificate);
179         } catch (CertificateNotYetValidException e) {
180             String errorMessage = e.getMessage();
181             if (errorMessage == null) {
182                 errorMessage = "root certificate not valid yet";
183             }
184 
185             if (HttpLog.LOGV) {
186                 HttpLog.v(errorMessage);
187             }
188 
189             error = new SslError(
190                 SslError.SSL_NOTYETVALID, currCertificate);
191         } catch (CertificateException e) {
192             String errorMessage = e.getMessage();
193             if (errorMessage == null) {
194                 errorMessage = "root certificate not trusted";
195             }
196 
197             if (HttpLog.LOGV) {
198                 HttpLog.v(errorMessage);
199             }
200 
201             return new SslError(
202                 SslError.SSL_UNTRUSTED, currCertificate);
203         }
204 
205         // Then go through the certificate chain checking that each
206         // certificate trusts the next and that each certificate is
207         // within its valid date range. Walk the chain in the order
208         // from the CA to the end-user
209         X509Certificate prevCertificate =
210             serverCertificates[serverCertificates.length - 1];
211 
212         for (int i = serverCertificates.length - 2; i >= 0; --i) {
213             currCertificate = serverCertificates[i];
214 
215             // if a certificate is null, we cannot verify the chain
216             if (currCertificate == null) {
217                 closeSocketThrowException(
218                     sslSocket, "null certificate in the chain");
219             }
220 
221             // verify if trusted by chain
222             if (!prevCertificate.getSubjectDN().equals(
223                     currCertificate.getIssuerDN())) {
224                 String errorMessage = "not trusted by chain";
225 
226                 if (HttpLog.LOGV) {
227                     HttpLog.v(errorMessage);
228                 }
229 
230                 return new SslError(
231                     SslError.SSL_UNTRUSTED, currCertificate);
232             }
233 
234             try {
235                 currCertificate.verify(prevCertificate.getPublicKey());
236             } catch (GeneralSecurityException e) {
237                 String errorMessage = e.getMessage();
238                 if (errorMessage == null) {
239                     errorMessage = "not trusted by chain";
240                 }
241 
242                 if (HttpLog.LOGV) {
243                     HttpLog.v(errorMessage);
244                 }
245 
246                 return new SslError(
247                     SslError.SSL_UNTRUSTED, currCertificate);
248             }
249 
250             // verify if the dates are valid
251             try {
252               currCertificate.checkValidity();
253             } catch (CertificateExpiredException e) {
254                 String errorMessage = e.getMessage();
255                 if (errorMessage == null) {
256                     errorMessage = "certificate expired";
257                 }
258 
259                 if (HttpLog.LOGV) {
260                     HttpLog.v(errorMessage);
261                 }
262 
263                 if (error == null ||
264                     error.getPrimaryError() < SslError.SSL_EXPIRED) {
265                     error = new SslError(
266                         SslError.SSL_EXPIRED, currCertificate);
267                 }
268             } catch (CertificateNotYetValidException e) {
269                 String errorMessage = e.getMessage();
270                 if (errorMessage == null) {
271                     errorMessage = "certificate not valid yet";
272                 }
273 
274                 if (HttpLog.LOGV) {
275                     HttpLog.v(errorMessage);
276                 }
277 
278                 if (error == null ||
279                     error.getPrimaryError() < SslError.SSL_NOTYETVALID) {
280                     error = new SslError(
281                         SslError.SSL_NOTYETVALID, currCertificate);
282                 }
283             }
284 
285             prevCertificate = currCertificate;
286         }
287 
288         // if we do not have an error to report back to the user, throw
289         // an exception (a generic error will be reported instead)
290         if (error == null) {
291             closeSocketThrowException(
292                 sslSocket,
293                 "failed to pre-validate the certificate chain due to a non-standard error");
294         }
295 
296         return error;
297     }
298 
closeSocketThrowException( SSLSocket socket, String errorMessage, String defaultErrorMessage)299     private void closeSocketThrowException(
300             SSLSocket socket, String errorMessage, String defaultErrorMessage)
301             throws IOException {
302         closeSocketThrowException(
303             socket, errorMessage != null ? errorMessage : defaultErrorMessage);
304     }
305 
closeSocketThrowException(SSLSocket socket, String errorMessage)306     private void closeSocketThrowException(SSLSocket socket,
307             String errorMessage) throws IOException {
308         if (HttpLog.LOGV) {
309             HttpLog.v("validation error: " + errorMessage);
310         }
311 
312         if (socket != null) {
313             SSLSession session = socket.getSession();
314             if (session != null) {
315                 session.invalidate();
316             }
317 
318             socket.close();
319         }
320 
321         throw new SSLHandshakeException(errorMessage);
322     }
323 }
324