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