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