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