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