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