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