CertificateChainValidator.java revision 28b1f0ee02e14241ffb81f431fc54053771c1c90
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
19
20import java.io.ByteArrayInputStream;
21import java.io.IOException;
22import java.security.GeneralSecurityException;
23import java.security.KeyManagementException;
24import java.security.cert.Certificate;
25import java.security.cert.CertificateException;
26import java.security.cert.CertificateFactory;
27import java.security.cert.X509Certificate;
28import javax.net.ssl.DefaultHostnameVerifier;
29import javax.net.ssl.SSLHandshakeException;
30import javax.net.ssl.SSLSession;
31import javax.net.ssl.SSLSocket;
32import javax.net.ssl.X509TrustManager;
33import org.apache.harmony.xnet.provider.jsse.SSLParametersImpl;
34import org.apache.harmony.xnet.provider.jsse.TrustManagerImpl;
35
36/**
37 * Class responsible for all server certificate validation functionality
38 *
39 * {@hide}
40 */
41public class CertificateChainValidator {
42
43    /**
44     * The singleton instance of the certificate chain validator
45     */
46    private static final CertificateChainValidator sInstance
47            = new CertificateChainValidator();
48
49    private static final DefaultHostnameVerifier sVerifier
50            = new DefaultHostnameVerifier();
51
52    /**
53     * @return The singleton instance of the certificates chain validator
54     */
55    public static CertificateChainValidator getInstance() {
56        return sInstance;
57    }
58
59    /**
60     * Creates a new certificate chain validator. This is a private constructor.
61     * If you need a Certificate chain validator, call getInstance().
62     */
63    private CertificateChainValidator() {}
64
65    /**
66     * Performs the handshake and server certificates validation
67     * Notice a new chain will be rebuilt by tracing the issuer and subject
68     * before calling checkServerTrusted().
69     * And if the last traced certificate is self issued and it is expired, it
70     * will be dropped.
71     * @param sslSocket The secure connection socket
72     * @param domain The website domain
73     * @return An SSL error object if there is an error and null otherwise
74     */
75    public SslError doHandshakeAndValidateServerCertificates(
76            HttpsConnection connection, SSLSocket sslSocket, String domain)
77            throws IOException {
78        // get a valid SSLSession, close the socket if we fail
79        SSLSession sslSession = sslSocket.getSession();
80        if (!sslSession.isValid()) {
81            closeSocketThrowException(sslSocket, "failed to perform SSL handshake");
82        }
83
84        // retrieve the chain of the server peer certificates
85        Certificate[] peerCertificates =
86            sslSocket.getSession().getPeerCertificates();
87
88        if (peerCertificates == null || peerCertificates.length == 0) {
89            closeSocketThrowException(
90                sslSocket, "failed to retrieve peer certificates");
91        } else {
92            // update the SSL certificate associated with the connection
93            if (connection != null) {
94                if (peerCertificates[0] != null) {
95                    connection.setCertificate(
96                        new SslCertificate((X509Certificate)peerCertificates[0]));
97                }
98            }
99        }
100
101        return verifyServerDomainAndCertificates((X509Certificate[]) peerCertificates, domain, "RSA");
102    }
103
104    /**
105     * Similar to doHandshakeAndValidateServerCertificates but exposed to JNI for use
106     * by Chromium HTTPS stack to validate the cert chain.
107     * @param certChain The bytes for certificates in ASN.1 DER encoded certificates format.
108     * @param domain The full website hostname and domain
109     * @param authType The authentication type for the cert chain
110     * @return An SSL error object if there is an error and null otherwise
111     */
112    public static SslError verifyServerCertificates(
113        byte[][] certChain, String domain, String authType)
114        throws IOException {
115
116        if (certChain == null || certChain.length == 0) {
117            throw new IllegalArgumentException("bad certificate chain");
118        }
119
120        X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
121
122        try {
123            CertificateFactory cf = CertificateFactory.getInstance("X.509");
124            for (int i = 0; i < certChain.length; ++i) {
125                serverCertificates[i] = (X509Certificate) cf.generateCertificate(
126                        new ByteArrayInputStream(certChain[i]));
127            }
128        } catch (CertificateException e) {
129            throw new IOException("can't read certificate", e);
130        }
131
132        return verifyServerDomainAndCertificates(serverCertificates, domain, authType);
133    }
134
135    /**
136     * Handles updates to credential storage.
137     */
138    public static void handleTrustStorageUpdate() {
139
140        try {
141            X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultTrustManager();
142            if( x509TrustManager instanceof TrustManagerImpl ) {
143                TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager;
144                trustManager.handleTrustStorageUpdate();
145            }
146        } catch (KeyManagementException ignored) {
147        }
148    }
149
150    /**
151     * Common code of doHandshakeAndValidateServerCertificates and verifyServerCertificates.
152     * Calls DomainNamevalidator to verify the domain, and TrustManager to verify the certs.
153     * @param chain the cert chain in X509 cert format.
154     * @param domain The full website hostname and domain
155     * @param authType The authentication type for the cert chain
156     * @return An SSL error object if there is an error and null otherwise
157     */
158    private static SslError verifyServerDomainAndCertificates(
159            X509Certificate[] chain, String domain, String authType)
160            throws IOException {
161        // check if the first certificate in the chain is for this site
162        X509Certificate currCertificate = chain[0];
163        if (currCertificate == null) {
164            throw new IllegalArgumentException("certificate for this site is null");
165        }
166
167        boolean valid = domain != null
168                && !domain.isEmpty()
169                && sVerifier.verify(domain, currCertificate);
170        if (!valid) {
171            if (HttpLog.LOGV) {
172                HttpLog.v("certificate not for this host: " + domain);
173            }
174            return new SslError(SslError.SSL_IDMISMATCH, currCertificate);
175        }
176
177        try {
178            X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultTrustManager();
179            if (x509TrustManager instanceof TrustManagerImpl) {
180                TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager;
181                trustManager.checkServerTrusted(chain, authType, domain);
182            } else {
183                x509TrustManager.checkServerTrusted(chain, authType);
184            }
185            return null;  // No errors.
186        } catch (GeneralSecurityException e) {
187            if (HttpLog.LOGV) {
188                HttpLog.v("failed to validate the certificate chain, error: " +
189                    e.getMessage());
190            }
191            return new SslError(SslError.SSL_UNTRUSTED, currCertificate);
192        }
193    }
194
195
196    private void closeSocketThrowException(
197            SSLSocket socket, String errorMessage, String defaultErrorMessage)
198            throws IOException {
199        closeSocketThrowException(
200            socket, errorMessage != null ? errorMessage : defaultErrorMessage);
201    }
202
203    private void closeSocketThrowException(SSLSocket socket,
204            String errorMessage) throws IOException {
205        if (HttpLog.LOGV) {
206            HttpLog.v("validation error: " + errorMessage);
207        }
208
209        if (socket != null) {
210            SSLSession session = socket.getSession();
211            if (session != null) {
212                session.invalidate();
213            }
214
215            socket.close();
216        }
217
218        throw new SSLHandshakeException(errorMessage);
219    }
220}
221