CertificateChainValidator.java revision 02ca44b13c7aa66f99242dbcb07feac877153754
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 com.android.internal.net.DomainNameValidator;
21
22import org.apache.harmony.xnet.provider.jsse.SSLParametersImpl;
23
24import java.io.IOException;
25
26import java.security.cert.Certificate;
27import java.security.cert.CertificateException;
28import java.security.cert.CertificateExpiredException;
29import java.security.cert.CertificateNotYetValidException;
30import java.security.cert.X509Certificate;
31import java.security.GeneralSecurityException;
32import java.security.KeyStore;
33import java.util.Date;
34
35import javax.net.ssl.SSLHandshakeException;
36import javax.net.ssl.SSLSession;
37import javax.net.ssl.SSLSocket;
38import javax.net.ssl.TrustManager;
39import javax.net.ssl.TrustManagerFactory;
40import javax.net.ssl.X509TrustManager;
41
42/**
43 * Class responsible for all server certificate validation functionality
44 *
45 * {@hide}
46 */
47class CertificateChainValidator {
48
49    /**
50     * The singleton instance of the certificate chain validator
51     */
52    private static final CertificateChainValidator sInstance
53            = new CertificateChainValidator();
54
55    /**
56     * @return The singleton instance of the certificates chain validator
57     */
58    public static CertificateChainValidator getInstance() {
59        return sInstance;
60    }
61
62    /**
63     * Creates a new certificate chain validator. This is a private constructor.
64     * If you need a Certificate chain validator, call getInstance().
65     */
66    private CertificateChainValidator() {}
67
68    /**
69     * Performs the handshake and server certificates validation
70     * Notice a new chain will be rebuilt by tracing the issuer and subject
71     * before calling checkServerTrusted().
72     * And if the last traced certificate is self issued and it is expired, it
73     * will be dropped.
74     * @param sslSocket The secure connection socket
75     * @param domain The website domain
76     * @return An SSL error object if there is an error and null otherwise
77     */
78    public SslError doHandshakeAndValidateServerCertificates(
79            HttpsConnection connection, SSLSocket sslSocket, String domain)
80            throws IOException {
81        X509Certificate[] serverCertificates = null;
82
83        // get a valid SSLSession, close the socket if we fail
84        SSLSession sslSession = sslSession = sslSocket.getSession();
85        if (!sslSession.isValid()) {
86            closeSocketThrowException(sslSocket, "failed to perform SSL handshake");
87        }
88
89        // retrieve the chain of the server peer certificates
90        Certificate[] peerCertificates =
91            sslSocket.getSession().getPeerCertificates();
92
93        if (peerCertificates == null || peerCertificates.length <= 0) {
94            closeSocketThrowException(
95                sslSocket, "failed to retrieve peer certificates");
96        } else {
97            serverCertificates =
98                new X509Certificate[peerCertificates.length];
99            for (int i = 0; i < peerCertificates.length; ++i) {
100                serverCertificates[i] =
101                    (X509Certificate)(peerCertificates[i]);
102            }
103
104            // update the SSL certificate associated with the connection
105            if (connection != null) {
106                if (serverCertificates[0] != null) {
107                    connection.setCertificate(
108                        new SslCertificate(serverCertificates[0]));
109                }
110            }
111        }
112
113        // check if the first certificate in the chain is for this site
114        X509Certificate currCertificate = serverCertificates[0];
115        if (currCertificate == null) {
116            closeSocketThrowException(
117                sslSocket, "certificate for this site is null");
118        } else {
119            if (!DomainNameValidator.match(currCertificate, domain)) {
120                String errorMessage = "certificate not for this host: " + domain;
121
122                if (HttpLog.LOGV) {
123                    HttpLog.v(errorMessage);
124                }
125
126                sslSocket.getSession().invalidate();
127                return new SslError(
128                    SslError.SSL_IDMISMATCH, currCertificate);
129            }
130        }
131
132        // first, we validate the new chain using the standard validation
133        // solution; if we do not find any errors, we are done; if we
134        // fail the standard validation, we re-validate again below,
135        // this time trying to retrieve any individual errors we can
136        // report back to the user.
137        //
138        try {
139            SSLParametersImpl.getDefaultTrustManager().checkServerTrusted(
140                serverCertificates, "RSA");
141
142            // no errors!!!
143            return null;
144        } catch (CertificateException e) {
145            sslSocket.getSession().invalidate();
146
147            if (HttpLog.LOGV) {
148                HttpLog.v(
149                    "failed to pre-validate the certificate chain, error: " +
150                    e.getMessage());
151            }
152            return new SslError(
153                SslError.SSL_UNTRUSTED, currCertificate);
154        }
155    }
156
157    private void closeSocketThrowException(
158            SSLSocket socket, String errorMessage, String defaultErrorMessage)
159            throws IOException {
160        closeSocketThrowException(
161            socket, errorMessage != null ? errorMessage : defaultErrorMessage);
162    }
163
164    private void closeSocketThrowException(SSLSocket socket,
165            String errorMessage) throws IOException {
166        if (HttpLog.LOGV) {
167            HttpLog.v("validation error: " + errorMessage);
168        }
169
170        if (socket != null) {
171            SSLSession session = socket.getSession();
172            if (session != null) {
173                session.invalidate();
174            }
175
176            socket.close();
177        }
178
179        throw new SSLHandshakeException(errorMessage);
180    }
181}
182