Home | History | Annotate | Download | only in http
      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