The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
Dianne Hackborn | 2269d157 | 2010-02-24 19:54:22 -0800 | [diff] [blame] | 19 | |
| 20 | import com.android.internal.net.DomainNameValidator; |
Makoto Onuki | 8f028a9 | 2010-01-08 13:34:57 -0800 | [diff] [blame] | 21 | |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 22 | import org.apache.harmony.security.provider.cert.X509CertImpl; |
Brian Carlstrom | 405d4db | 2010-09-14 00:26:53 -0700 | [diff] [blame] | 23 | import org.apache.harmony.xnet.provider.jsse.SSLParametersImpl; |
Bob Lee | e97c2006 | 2009-08-20 17:36:11 -0700 | [diff] [blame] | 24 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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; |
Huahui Wu | c4e834d | 2010-01-08 17:46:14 -0500 | [diff] [blame] | 34 | import java.util.Date; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | */ |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 53 | private static final CertificateChainValidator sInstance |
| 54 | = new CertificateChainValidator(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 55 | |
| 56 | /** |
Huahui Wu | c4e834d | 2010-01-08 17:46:14 -0500 | [diff] [blame] | 57 | * @return The singleton instance of the certificates chain validator |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 58 | */ |
| 59 | public static CertificateChainValidator getInstance() { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 60 | return sInstance; |
| 61 | } |
| 62 | |
| 63 | /** |
Huahui Wu | c4e834d | 2010-01-08 17:46:14 -0500 | [diff] [blame] | 64 | * Creates a new certificate chain validator. This is a private constructor. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 65 | * If you need a Certificate chain validator, call getInstance(). |
| 66 | */ |
Bob Lee | e97c2006 | 2009-08-20 17:36:11 -0700 | [diff] [blame] | 67 | private CertificateChainValidator() {} |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 68 | |
| 69 | /** |
| 70 | * Performs the handshake and server certificates validation |
Huahui Wu | c4e834d | 2010-01-08 17:46:14 -0500 | [diff] [blame] | 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. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 { |
Brian Carlstrom | e103355 | 2010-05-05 10:18:04 -0700 | [diff] [blame] | 82 | // get a valid SSLSession, close the socket if we fail |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 83 | SSLSession sslSession = sslSocket.getSession(); |
Brian Carlstrom | e103355 | 2010-05-05 10:18:04 -0700 | [diff] [blame] | 84 | if (!sslSession.isValid()) { |
| 85 | closeSocketThrowException(sslSocket, "failed to perform SSL handshake"); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 86 | } |
| 87 | |
| 88 | // retrieve the chain of the server peer certificates |
| 89 | Certificate[] peerCertificates = |
| 90 | sslSocket.getSession().getPeerCertificates(); |
| 91 | |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 92 | if (peerCertificates == null || peerCertificates.length == 0) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 93 | closeSocketThrowException( |
| 94 | sslSocket, "failed to retrieve peer certificates"); |
| 95 | } else { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 96 | // update the SSL certificate associated with the connection |
| 97 | if (connection != null) { |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 98 | if (peerCertificates[0] != null) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 99 | connection.setCertificate( |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 100 | new SslCertificate((X509Certificate)peerCertificates[0])); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 101 | } |
| 102 | } |
| 103 | } |
| 104 | |
Huahui Wu | 8234bdb | 2010-11-09 09:42:03 -0800 | [diff] [blame] | 105 | return verifyServerDomainAndCertificates((X509Certificate[]) peerCertificates, domain, "RSA"); |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 106 | } |
| 107 | |
| 108 | /** |
| 109 | * Similar to doHandshakeAndValidateServerCertificates but exposed to JNI for use |
| 110 | * by Chromium HTTPS stack to validate the cert chain. |
Huahui Wu | 8234bdb | 2010-11-09 09:42:03 -0800 | [diff] [blame] | 111 | * @param certChain The bytes for certificates in ASN.1 DER encoded certificates format. |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 112 | * @param domain The full website hostname and domain |
Huahui Wu | 8234bdb | 2010-11-09 09:42:03 -0800 | [diff] [blame] | 113 | * @param authType The authentication type for the cert chain |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 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 | |
Huahui Wu | 8234bdb | 2010-11-09 09:42:03 -0800 | [diff] [blame] | 130 | return verifyServerDomainAndCertificates(serverCertificates, domain, authType); |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 131 | } |
| 132 | |
| 133 | /** |
| 134 | * Common code of doHandshakeAndValidateServerCertificates and verifyServerCertificates. |
Huahui Wu | 8234bdb | 2010-11-09 09:42:03 -0800 | [diff] [blame] | 135 | * Calls DomainNamevalidator to verify the domain, and TrustManager to verify the certs. |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 136 | * @param chain the cert chain in X509 cert format. |
| 137 | * @param domain The full website hostname and domain |
Huahui Wu | 8234bdb | 2010-11-09 09:42:03 -0800 | [diff] [blame] | 138 | * @param authType The authentication type for the cert chain |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 139 | * @return An SSL error object if there is an error and null otherwise |
| 140 | */ |
| 141 | private static SslError verifyServerDomainAndCertificates( |
Huahui Wu | 8234bdb | 2010-11-09 09:42:03 -0800 | [diff] [blame] | 142 | X509Certificate[] chain, String domain, String authType) |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 143 | throws IOException { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 144 | // check if the first certificate in the chain is for this site |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 145 | X509Certificate currCertificate = chain[0]; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 146 | if (currCertificate == null) { |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 147 | throw new IllegalArgumentException("certificate for this site is null"); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 148 | } |
| 149 | |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 150 | if (!DomainNameValidator.match(currCertificate, domain)) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 151 | if (HttpLog.LOGV) { |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 152 | HttpLog.v("certificate not for this host: " + domain); |
| 153 | } |
| 154 | return new SslError(SslError.SSL_IDMISMATCH, currCertificate); |
| 155 | } |
| 156 | |
| 157 | try { |
Huahui Wu | 8234bdb | 2010-11-09 09:42:03 -0800 | [diff] [blame] | 158 | SSLParametersImpl.getDefaultTrustManager().checkServerTrusted(chain, authType); |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 159 | return null; // No errors. |
| 160 | } catch (CertificateException e) { |
| 161 | if (HttpLog.LOGV) { |
| 162 | HttpLog.v("failed to validate the certificate chain, error: " + |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 163 | e.getMessage()); |
| 164 | } |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 165 | return new SslError(SslError.SSL_UNTRUSTED, currCertificate); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 166 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 167 | } |
| 168 | |
Huahui Wu | 85ffa26 | 2010-11-08 12:02:50 -0800 | [diff] [blame] | 169 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | } |