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