blob: 92be37392912f86253dfb8de1a3a9cec415923a1 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Dianne Hackborn2269d1572010-02-24 19:54:22 -080019
20import com.android.internal.net.DomainNameValidator;
Makoto Onuki8f028a92010-01-08 13:34:57 -080021
Huahui Wu85ffa262010-11-08 12:02:50 -080022import org.apache.harmony.security.provider.cert.X509CertImpl;
Brian Carlstrom405d4db2010-09-14 00:26:53 -070023import org.apache.harmony.xnet.provider.jsse.SSLParametersImpl;
Bob Leee97c20062009-08-20 17:36:11 -070024
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import 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;
Huahui Wuc4e834d2010-01-08 17:46:14 -050034import java.util.Date;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
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 */
Bob Lee886f3d62009-03-24 20:06:51 -070053 private static final CertificateChainValidator sInstance
54 = new CertificateChainValidator();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055
56 /**
Huahui Wuc4e834d2010-01-08 17:46:14 -050057 * @return The singleton instance of the certificates chain validator
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058 */
59 public static CertificateChainValidator getInstance() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080060 return sInstance;
61 }
62
63 /**
Huahui Wuc4e834d2010-01-08 17:46:14 -050064 * Creates a new certificate chain validator. This is a private constructor.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 * If you need a Certificate chain validator, call getInstance().
66 */
Bob Leee97c20062009-08-20 17:36:11 -070067 private CertificateChainValidator() {}
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068
69 /**
70 * Performs the handshake and server certificates validation
Huahui Wuc4e834d2010-01-08 17:46:14 -050071 * 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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075 * @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 {
Brian Carlstrome1033552010-05-05 10:18:04 -070082 // get a valid SSLSession, close the socket if we fail
Huahui Wu85ffa262010-11-08 12:02:50 -080083 SSLSession sslSession = sslSocket.getSession();
Brian Carlstrome1033552010-05-05 10:18:04 -070084 if (!sslSession.isValid()) {
85 closeSocketThrowException(sslSocket, "failed to perform SSL handshake");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086 }
87
88 // retrieve the chain of the server peer certificates
89 Certificate[] peerCertificates =
90 sslSocket.getSession().getPeerCertificates();
91
Huahui Wu85ffa262010-11-08 12:02:50 -080092 if (peerCertificates == null || peerCertificates.length == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080093 closeSocketThrowException(
94 sslSocket, "failed to retrieve peer certificates");
95 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 // update the SSL certificate associated with the connection
97 if (connection != null) {
Huahui Wu85ffa262010-11-08 12:02:50 -080098 if (peerCertificates[0] != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099 connection.setCertificate(
Huahui Wu85ffa262010-11-08 12:02:50 -0800100 new SslCertificate((X509Certificate)peerCertificates[0]));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101 }
102 }
103 }
104
Huahui Wu8234bdb2010-11-09 09:42:03 -0800105 return verifyServerDomainAndCertificates((X509Certificate[]) peerCertificates, domain, "RSA");
Huahui Wu85ffa262010-11-08 12:02:50 -0800106 }
107
108 /**
109 * Similar to doHandshakeAndValidateServerCertificates but exposed to JNI for use
110 * by Chromium HTTPS stack to validate the cert chain.
Huahui Wu8234bdb2010-11-09 09:42:03 -0800111 * @param certChain The bytes for certificates in ASN.1 DER encoded certificates format.
Huahui Wu85ffa262010-11-08 12:02:50 -0800112 * @param domain The full website hostname and domain
Huahui Wu8234bdb2010-11-09 09:42:03 -0800113 * @param authType The authentication type for the cert chain
Huahui Wu85ffa262010-11-08 12:02:50 -0800114 * @return An SSL error object if there is an error and null otherwise
115 */
116 public static SslError verifyServerCertificates(
117 byte[][] certChain, String domain, String authType)
118 throws IOException {
119
120 if (certChain == null || certChain.length == 0) {
121 throw new IllegalArgumentException("bad certificate chain");
122 }
123
124 X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
125
126 for (int i = 0; i < certChain.length; ++i) {
127 serverCertificates[i] = new X509CertImpl(certChain[i]);
128 }
129
Huahui Wu8234bdb2010-11-09 09:42:03 -0800130 return verifyServerDomainAndCertificates(serverCertificates, domain, authType);
Huahui Wu85ffa262010-11-08 12:02:50 -0800131 }
132
133 /**
134 * Common code of doHandshakeAndValidateServerCertificates and verifyServerCertificates.
Huahui Wu8234bdb2010-11-09 09:42:03 -0800135 * Calls DomainNamevalidator to verify the domain, and TrustManager to verify the certs.
Huahui Wu85ffa262010-11-08 12:02:50 -0800136 * @param chain the cert chain in X509 cert format.
137 * @param domain The full website hostname and domain
Huahui Wu8234bdb2010-11-09 09:42:03 -0800138 * @param authType The authentication type for the cert chain
Huahui Wu85ffa262010-11-08 12:02:50 -0800139 * @return An SSL error object if there is an error and null otherwise
140 */
141 private static SslError verifyServerDomainAndCertificates(
Huahui Wu8234bdb2010-11-09 09:42:03 -0800142 X509Certificate[] chain, String domain, String authType)
Huahui Wu85ffa262010-11-08 12:02:50 -0800143 throws IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800144 // check if the first certificate in the chain is for this site
Huahui Wu85ffa262010-11-08 12:02:50 -0800145 X509Certificate currCertificate = chain[0];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 if (currCertificate == null) {
Huahui Wu85ffa262010-11-08 12:02:50 -0800147 throw new IllegalArgumentException("certificate for this site is null");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800148 }
149
Huahui Wu85ffa262010-11-08 12:02:50 -0800150 if (!DomainNameValidator.match(currCertificate, domain)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800151 if (HttpLog.LOGV) {
Huahui Wu85ffa262010-11-08 12:02:50 -0800152 HttpLog.v("certificate not for this host: " + domain);
153 }
154 return new SslError(SslError.SSL_IDMISMATCH, currCertificate);
155 }
156
157 try {
Huahui Wu8234bdb2010-11-09 09:42:03 -0800158 SSLParametersImpl.getDefaultTrustManager().checkServerTrusted(chain, authType);
Huahui Wu85ffa262010-11-08 12:02:50 -0800159 return null; // No errors.
160 } catch (CertificateException e) {
161 if (HttpLog.LOGV) {
162 HttpLog.v("failed to validate the certificate chain, error: " +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800163 e.getMessage());
164 }
Huahui Wu85ffa262010-11-08 12:02:50 -0800165 return new SslError(SslError.SSL_UNTRUSTED, currCertificate);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800166 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 }
168
Huahui Wu85ffa262010-11-08 12:02:50 -0800169
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800170 private void closeSocketThrowException(
171 SSLSocket socket, String errorMessage, String defaultErrorMessage)
172 throws IOException {
173 closeSocketThrowException(
174 socket, errorMessage != null ? errorMessage : defaultErrorMessage);
175 }
176
177 private void closeSocketThrowException(SSLSocket socket,
178 String errorMessage) throws IOException {
179 if (HttpLog.LOGV) {
180 HttpLog.v("validation error: " + errorMessage);
181 }
182
183 if (socket != null) {
184 SSLSession session = socket.getSession();
185 if (session != null) {
186 session.invalidate();
187 }
188
189 socket.close();
190 }
191
192 throw new SSLHandshakeException(errorMessage);
193 }
194}