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