Merge "Add support for sending touch events in DRT."
diff --git a/core/java/android/net/http/CertificateChainValidator.java b/core/java/android/net/http/CertificateChainValidator.java
index da6af9d..e1327dd 100644
--- a/core/java/android/net/http/CertificateChainValidator.java
+++ b/core/java/android/net/http/CertificateChainValidator.java
@@ -29,6 +29,7 @@
 import java.security.cert.X509Certificate;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
+import java.util.Date;
 
 import javax.net.ssl.SSLHandshakeException;
 import javax.net.ssl.SSLSession;
@@ -51,20 +52,24 @@
             = new CertificateChainValidator();
 
     /**
-     * @return The singleton instance of the certificator chain validator
+     * @return The singleton instance of the certificates chain validator
      */
     public static CertificateChainValidator getInstance() {
         return sInstance;
     }
 
     /**
-     * Creates a new certificate chain validator. This is a pivate constructor.
+     * Creates a new certificate chain validator. This is a private constructor.
      * If you need a Certificate chain validator, call getInstance().
      */
     private CertificateChainValidator() {}
 
     /**
      * Performs the handshake and server certificates validation
+     * Notice a new chain will be rebuilt by tracing the issuer and subject
+     * before calling checkServerTrusted().
+     * And if the last traced certificate is self issued and it is expired, it
+     * will be dropped.
      * @param sslSocket The secure connection socket
      * @param domain The website domain
      * @return An SSL error object if there is an error and null otherwise
@@ -127,7 +132,58 @@
             }
         }
 
-        // first, we validate the chain using the standard validation
+        // Clean up the certificates chain and build a new one.
+        // Theoretically, we shouldn't have to do this, but various web servers
+        // in practice are mis-configured to have out-of-order certificates or
+        // expired self-issued root certificate.
+        int chainLength = serverCertificates.length;
+        if (serverCertificates.length > 1) {
+          // 1. we clean the received certificates chain.
+          // We start from the end-entity certificate, tracing down by matching
+          // the "issuer" field and "subject" field until we can't continue.
+          // This helps when the certificates are out of order or
+          // some certificates are not related to the site.
+          int currIndex;
+          for (currIndex = 0; currIndex < serverCertificates.length; ++currIndex) {
+            boolean foundNext = false;
+            for (int nextIndex = currIndex + 1;
+                 nextIndex < serverCertificates.length;
+                 ++nextIndex) {
+              if (serverCertificates[currIndex].getIssuerDN().equals(
+                  serverCertificates[nextIndex].getSubjectDN())) {
+                foundNext = true;
+                // Exchange certificates so that 0 through currIndex + 1 are in proper order
+                if (nextIndex != currIndex + 1) {
+                  X509Certificate tempCertificate = serverCertificates[nextIndex];
+                  serverCertificates[nextIndex] = serverCertificates[currIndex + 1];
+                  serverCertificates[currIndex + 1] = tempCertificate;
+                }
+                break;
+              }
+            }
+            if (!foundNext) break;
+          }
+
+          // 2. we exam if the last traced certificate is self issued and it is expired.
+          // If so, we drop it and pass the rest to checkServerTrusted(), hoping we might
+          // have a similar but unexpired trusted root.
+          chainLength = currIndex + 1;
+          X509Certificate lastCertificate = serverCertificates[chainLength - 1];
+          Date now = new Date();
+          if (lastCertificate.getSubjectDN().equals(lastCertificate.getIssuerDN())
+              && now.after(lastCertificate.getNotAfter())) {
+            --chainLength;
+          }
+        }
+
+        // 3. Now we copy the newly built chain into an appropriately sized array.
+        X509Certificate[] newServerCertificates = null;
+        newServerCertificates = new X509Certificate[chainLength];
+        for (int i = 0; i < chainLength; ++i) {
+          newServerCertificates[i] = serverCertificates[i];
+        }
+
+        // first, we validate the new chain using the standard validation
         // solution; if we do not find any errors, we are done; if we
         // fail the standard validation, we re-validate again below,
         // this time trying to retrieve any individual errors we can
@@ -135,167 +191,21 @@
         //
         try {
             SSLParameters.getDefaultTrustManager().checkServerTrusted(
-                serverCertificates, "RSA");
+                newServerCertificates, "RSA");
 
             // no errors!!!
             return null;
         } catch (CertificateException e) {
+            sslSocket.getSession().invalidate();
+
             if (HttpLog.LOGV) {
                 HttpLog.v(
                     "failed to pre-validate the certificate chain, error: " +
                     e.getMessage());
             }
-        }
-
-        sslSocket.getSession().invalidate();
-
-        SslError error = null;
-
-        // we check the root certificate separately from the rest of the
-        // chain; this is because we need to know what certificate in
-        // the chain resulted in an error if any
-        currCertificate =
-            serverCertificates[serverCertificates.length - 1];
-        if (currCertificate == null) {
-            closeSocketThrowException(
-                sslSocket, "root certificate is null");
-        }
-
-        // check if the last certificate in the chain (root) is trusted
-        X509Certificate[] rootCertificateChain = { currCertificate };
-        try {
-            SSLParameters.getDefaultTrustManager().checkServerTrusted(
-                rootCertificateChain, "RSA");
-        } catch (CertificateExpiredException e) {
-            String errorMessage = e.getMessage();
-            if (errorMessage == null) {
-                errorMessage = "root certificate has expired";
-            }
-
-            if (HttpLog.LOGV) {
-                HttpLog.v(errorMessage);
-            }
-
-            error = new SslError(
-                SslError.SSL_EXPIRED, currCertificate);
-        } catch (CertificateNotYetValidException e) {
-            String errorMessage = e.getMessage();
-            if (errorMessage == null) {
-                errorMessage = "root certificate not valid yet";
-            }
-
-            if (HttpLog.LOGV) {
-                HttpLog.v(errorMessage);
-            }
-
-            error = new SslError(
-                SslError.SSL_NOTYETVALID, currCertificate);
-        } catch (CertificateException e) {
-            String errorMessage = e.getMessage();
-            if (errorMessage == null) {
-                errorMessage = "root certificate not trusted";
-            }
-
-            if (HttpLog.LOGV) {
-                HttpLog.v(errorMessage);
-            }
-
             return new SslError(
                 SslError.SSL_UNTRUSTED, currCertificate);
         }
-
-        // Then go through the certificate chain checking that each
-        // certificate trusts the next and that each certificate is
-        // within its valid date range. Walk the chain in the order
-        // from the CA to the end-user
-        X509Certificate prevCertificate =
-            serverCertificates[serverCertificates.length - 1];
-
-        for (int i = serverCertificates.length - 2; i >= 0; --i) {
-            currCertificate = serverCertificates[i];
-
-            // if a certificate is null, we cannot verify the chain
-            if (currCertificate == null) {
-                closeSocketThrowException(
-                    sslSocket, "null certificate in the chain");
-            }
-
-            // verify if trusted by chain
-            if (!prevCertificate.getSubjectDN().equals(
-                    currCertificate.getIssuerDN())) {
-                String errorMessage = "not trusted by chain";
-
-                if (HttpLog.LOGV) {
-                    HttpLog.v(errorMessage);
-                }
-
-                return new SslError(
-                    SslError.SSL_UNTRUSTED, currCertificate);
-            }
-
-            try {
-                currCertificate.verify(prevCertificate.getPublicKey());
-            } catch (GeneralSecurityException e) {
-                String errorMessage = e.getMessage();
-                if (errorMessage == null) {
-                    errorMessage = "not trusted by chain";
-                }
-
-                if (HttpLog.LOGV) {
-                    HttpLog.v(errorMessage);
-                }
-
-                return new SslError(
-                    SslError.SSL_UNTRUSTED, currCertificate);
-            }
-
-            // verify if the dates are valid
-            try {
-              currCertificate.checkValidity();
-            } catch (CertificateExpiredException e) {
-                String errorMessage = e.getMessage();
-                if (errorMessage == null) {
-                    errorMessage = "certificate expired";
-                }
-
-                if (HttpLog.LOGV) {
-                    HttpLog.v(errorMessage);
-                }
-
-                if (error == null ||
-                    error.getPrimaryError() < SslError.SSL_EXPIRED) {
-                    error = new SslError(
-                        SslError.SSL_EXPIRED, currCertificate);
-                }
-            } catch (CertificateNotYetValidException e) {
-                String errorMessage = e.getMessage();
-                if (errorMessage == null) {
-                    errorMessage = "certificate not valid yet";
-                }
-
-                if (HttpLog.LOGV) {
-                    HttpLog.v(errorMessage);
-                }
-
-                if (error == null ||
-                    error.getPrimaryError() < SslError.SSL_NOTYETVALID) {
-                    error = new SslError(
-                        SslError.SSL_NOTYETVALID, currCertificate);
-                }
-            }
-
-            prevCertificate = currCertificate;
-        }
-
-        // if we do not have an error to report back to the user, throw
-        // an exception (a generic error will be reported instead)
-        if (error == null) {
-            closeSocketThrowException(
-                sslSocket,
-                "failed to pre-validate the certificate chain due to a non-standard error");
-        }
-
-        return error;
     }
 
     private void closeSocketThrowException(