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 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