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 import com.android.org.conscrypt.SSLParametersImpl; 20 import com.android.org.conscrypt.TrustManagerImpl; 21 22 import android.util.Slog; 23 24 import java.io.ByteArrayInputStream; 25 import java.io.IOException; 26 import java.lang.reflect.Method; 27 import java.security.GeneralSecurityException; 28 import java.security.KeyStore; 29 import java.security.KeyStoreException; 30 import java.security.NoSuchAlgorithmException; 31 import java.security.cert.Certificate; 32 import java.security.cert.CertificateException; 33 import java.security.cert.CertificateFactory; 34 import java.security.cert.X509Certificate; 35 36 import javax.net.ssl.HostnameVerifier; 37 import javax.net.ssl.HttpsURLConnection; 38 import javax.net.ssl.SSLHandshakeException; 39 import javax.net.ssl.SSLSession; 40 import javax.net.ssl.SSLSocket; 41 import javax.net.ssl.TrustManager; 42 import javax.net.ssl.TrustManagerFactory; 43 import javax.net.ssl.X509TrustManager; 44 45 /** 46 * Class responsible for all server certificate validation functionality 47 * 48 * {@hide} 49 */ 50 public class CertificateChainValidator { 51 private static final String TAG = "CertificateChainValidator"; 52 53 private static class NoPreloadHolder { 54 /** 55 * The singleton instance of the certificate chain validator. 56 */ 57 private static final CertificateChainValidator sInstance = new CertificateChainValidator(); 58 59 /** 60 * The singleton instance of the hostname verifier. 61 */ 62 private static final HostnameVerifier sVerifier = HttpsURLConnection 63 .getDefaultHostnameVerifier(); 64 } 65 66 private X509TrustManager mTrustManager; 67 68 /** 69 * @return The singleton instance of the certificates chain validator 70 */ 71 public static CertificateChainValidator getInstance() { 72 return NoPreloadHolder.sInstance; 73 } 74 75 /** 76 * Creates a new certificate chain validator. This is a private constructor. 77 * If you need a Certificate chain validator, call getInstance(). 78 */ 79 private CertificateChainValidator() { 80 try { 81 TrustManagerFactory tmf = TrustManagerFactory.getInstance("X.509"); 82 tmf.init((KeyStore) null); 83 for (TrustManager tm : tmf.getTrustManagers()) { 84 if (tm instanceof X509TrustManager) { 85 mTrustManager = (X509TrustManager) tm; 86 } 87 } 88 } catch (NoSuchAlgorithmException e) { 89 throw new RuntimeException("X.509 TrustManagerFactory must be available", e); 90 } catch (KeyStoreException e) { 91 throw new RuntimeException("X.509 TrustManagerFactory cannot be initialized", e); 92 } 93 94 if (mTrustManager == null) { 95 throw new RuntimeException( 96 "None of the X.509 TrustManagers are X509TrustManager"); 97 } 98 } 99 100 /** 101 * Performs the handshake and server certificates validation 102 * Notice a new chain will be rebuilt by tracing the issuer and subject 103 * before calling checkServerTrusted(). 104 * And if the last traced certificate is self issued and it is expired, it 105 * will be dropped. 106 * @param sslSocket The secure connection socket 107 * @param domain The website domain 108 * @return An SSL error object if there is an error and null otherwise 109 */ 110 public SslError doHandshakeAndValidateServerCertificates( 111 HttpsConnection connection, SSLSocket sslSocket, String domain) 112 throws IOException { 113 // get a valid SSLSession, close the socket if we fail 114 SSLSession sslSession = sslSocket.getSession(); 115 if (!sslSession.isValid()) { 116 closeSocketThrowException(sslSocket, "failed to perform SSL handshake"); 117 } 118 119 // retrieve the chain of the server peer certificates 120 Certificate[] peerCertificates = 121 sslSocket.getSession().getPeerCertificates(); 122 123 if (peerCertificates == null || peerCertificates.length == 0) { 124 closeSocketThrowException( 125 sslSocket, "failed to retrieve peer certificates"); 126 } else { 127 // update the SSL certificate associated with the connection 128 if (connection != null) { 129 if (peerCertificates[0] != null) { 130 connection.setCertificate( 131 new SslCertificate((X509Certificate)peerCertificates[0])); 132 } 133 } 134 } 135 136 return verifyServerDomainAndCertificates((X509Certificate[]) peerCertificates, domain, "RSA"); 137 } 138 139 /** 140 * Similar to doHandshakeAndValidateServerCertificates but exposed to JNI for use 141 * by Chromium HTTPS stack to validate the cert chain. 142 * @param certChain The bytes for certificates in ASN.1 DER encoded certificates format. 143 * @param domain The full website hostname and domain 144 * @param authType The authentication type for the cert chain 145 * @return An SSL error object if there is an error and null otherwise 146 */ 147 public static SslError verifyServerCertificates( 148 byte[][] certChain, String domain, String authType) 149 throws IOException { 150 151 if (certChain == null || certChain.length == 0) { 152 throw new IllegalArgumentException("bad certificate chain"); 153 } 154 155 X509Certificate[] serverCertificates = new X509Certificate[certChain.length]; 156 157 try { 158 CertificateFactory cf = CertificateFactory.getInstance("X.509"); 159 for (int i = 0; i < certChain.length; ++i) { 160 serverCertificates[i] = (X509Certificate) cf.generateCertificate( 161 new ByteArrayInputStream(certChain[i])); 162 } 163 } catch (CertificateException e) { 164 throw new IOException("can't read certificate", e); 165 } 166 167 return verifyServerDomainAndCertificates(serverCertificates, domain, authType); 168 } 169 170 /** 171 * Handles updates to credential storage. 172 */ 173 public static void handleTrustStorageUpdate() { 174 TrustManagerFactory tmf; 175 try { 176 tmf = TrustManagerFactory.getInstance("X.509"); 177 tmf.init((KeyStore) null); 178 } catch (NoSuchAlgorithmException e) { 179 Slog.w(TAG, "Couldn't find default X.509 TrustManagerFactory"); 180 return; 181 } catch (KeyStoreException e) { 182 Slog.w(TAG, "Couldn't initialize default X.509 TrustManagerFactory", e); 183 return; 184 } 185 186 TrustManager[] tms = tmf.getTrustManagers(); 187 boolean sentUpdate = false; 188 for (TrustManager tm : tms) { 189 try { 190 Method updateMethod = tm.getClass().getDeclaredMethod("handleTrustStorageUpdate"); 191 updateMethod.setAccessible(true); 192 updateMethod.invoke(tm); 193 sentUpdate = true; 194 } catch (Exception e) { 195 } 196 } 197 if (!sentUpdate) { 198 Slog.w(TAG, "Didn't find a TrustManager to handle CA list update"); 199 } 200 } 201 202 /** 203 * Common code of doHandshakeAndValidateServerCertificates and verifyServerCertificates. 204 * Calls DomainNamevalidator to verify the domain, and TrustManager to verify the certs. 205 * @param chain the cert chain in X509 cert format. 206 * @param domain The full website hostname and domain 207 * @param authType The authentication type for the cert chain 208 * @return An SSL error object if there is an error and null otherwise 209 */ 210 private static SslError verifyServerDomainAndCertificates( 211 X509Certificate[] chain, String domain, String authType) 212 throws IOException { 213 // check if the first certificate in the chain is for this site 214 X509Certificate currCertificate = chain[0]; 215 if (currCertificate == null) { 216 throw new IllegalArgumentException("certificate for this site is null"); 217 } 218 219 boolean valid = domain != null 220 && !domain.isEmpty() 221 && NoPreloadHolder.sVerifier.verify(domain, 222 new DelegatingSSLSession.CertificateWrap(currCertificate)); 223 if (!valid) { 224 if (HttpLog.LOGV) { 225 HttpLog.v("certificate not for this host: " + domain); 226 } 227 return new SslError(SslError.SSL_IDMISMATCH, currCertificate); 228 } 229 230 try { 231 X509TrustManager x509TrustManager = SSLParametersImpl.getDefaultX509TrustManager(); 232 if (x509TrustManager instanceof TrustManagerImpl) { 233 TrustManagerImpl trustManager = (TrustManagerImpl) x509TrustManager; 234 trustManager.checkServerTrusted(chain, authType, domain); 235 } else { 236 x509TrustManager.checkServerTrusted(chain, authType); 237 } 238 return null; // No errors. 239 } catch (GeneralSecurityException e) { 240 if (HttpLog.LOGV) { 241 HttpLog.v("failed to validate the certificate chain, error: " + 242 e.getMessage()); 243 } 244 return new SslError(SslError.SSL_UNTRUSTED, currCertificate); 245 } 246 } 247 248 /** 249 * Returns the platform default {@link X509TrustManager}. 250 */ 251 private X509TrustManager getTrustManager() { 252 return mTrustManager; 253 } 254 255 private void closeSocketThrowException( 256 SSLSocket socket, String errorMessage, String defaultErrorMessage) 257 throws IOException { 258 closeSocketThrowException( 259 socket, errorMessage != null ? errorMessage : defaultErrorMessage); 260 } 261 262 private void closeSocketThrowException(SSLSocket socket, 263 String errorMessage) throws IOException { 264 if (HttpLog.LOGV) { 265 HttpLog.v("validation error: " + errorMessage); 266 } 267 268 if (socket != null) { 269 SSLSession session = socket.getSession(); 270 if (session != null) { 271 session.invalidate(); 272 } 273 274 socket.close(); 275 } 276 277 throw new SSLHandshakeException(errorMessage); 278 } 279 } 280