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