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