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