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