The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2007 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 android.content.Context; |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 20 | import android.util.Log; |
Kenny Root | 12e7522 | 2013-04-23 22:34:24 -0700 | [diff] [blame] | 21 | import com.android.org.conscrypt.FileClientSessionCache; |
| 22 | import com.android.org.conscrypt.OpenSSLContextImpl; |
| 23 | import com.android.org.conscrypt.SSLClientSessionCache; |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 24 | import org.apache.http.Header; |
| 25 | import org.apache.http.HttpException; |
| 26 | import org.apache.http.HttpHost; |
| 27 | import org.apache.http.HttpStatus; |
| 28 | import org.apache.http.ParseException; |
| 29 | import org.apache.http.ProtocolVersion; |
| 30 | import org.apache.http.StatusLine; |
| 31 | import org.apache.http.message.BasicHttpRequest; |
| 32 | import org.apache.http.params.BasicHttpParams; |
| 33 | import org.apache.http.params.HttpConnectionParams; |
| 34 | import org.apache.http.params.HttpParams; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 35 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 36 | import javax.net.ssl.SSLException; |
| 37 | import javax.net.ssl.SSLSocket; |
| 38 | import javax.net.ssl.SSLSocketFactory; |
| 39 | import javax.net.ssl.TrustManager; |
| 40 | import javax.net.ssl.X509TrustManager; |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 41 | import java.io.File; |
| 42 | import java.io.IOException; |
| 43 | import java.net.InetSocketAddress; |
| 44 | import java.net.Socket; |
| 45 | import java.security.KeyManagementException; |
| 46 | import java.security.cert.X509Certificate; |
Elliott Hughes | cb64d43 | 2013-08-02 10:00:44 -0700 | [diff] [blame] | 47 | import java.util.Locale; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 48 | |
| 49 | /** |
| 50 | * A Connection connecting to a secure http server or tunneling through |
| 51 | * a http proxy server to a https server. |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 52 | * |
| 53 | * @hide |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 54 | */ |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 55 | public class HttpsConnection extends Connection { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 56 | |
| 57 | /** |
| 58 | * SSL socket factory |
| 59 | */ |
| 60 | private static SSLSocketFactory mSslSocketFactory = null; |
| 61 | |
| 62 | static { |
Brian Carlstrom | 37254dc | 2010-03-02 13:30:04 -0800 | [diff] [blame] | 63 | // This initialization happens in the zygote. It triggers some |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 64 | // lazy initialization that can will benefit later invocations of |
| 65 | // initializeEngine(). |
| 66 | initializeEngine(null); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 67 | } |
| 68 | |
| 69 | /** |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 70 | * @hide |
| 71 | * |
| 72 | * @param sessionDir directory to cache SSL sessions |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 73 | */ |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 74 | public static void initializeEngine(File sessionDir) { |
| 75 | try { |
| 76 | SSLClientSessionCache cache = null; |
| 77 | if (sessionDir != null) { |
| 78 | Log.d("HttpsConnection", "Caching SSL sessions in " |
| 79 | + sessionDir + "."); |
| 80 | cache = FileClientSessionCache.usingDirectory(sessionDir); |
| 81 | } |
| 82 | |
Brian Carlstrom | 3c7c351 | 2010-08-04 15:44:39 -0700 | [diff] [blame] | 83 | OpenSSLContextImpl sslContext = new OpenSSLContextImpl(); |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 84 | |
| 85 | // here, trust managers is a single trust-all manager |
| 86 | TrustManager[] trustManagers = new TrustManager[] { |
| 87 | new X509TrustManager() { |
| 88 | public X509Certificate[] getAcceptedIssuers() { |
| 89 | return null; |
| 90 | } |
| 91 | |
| 92 | public void checkClientTrusted( |
| 93 | X509Certificate[] certs, String authType) { |
| 94 | } |
| 95 | |
| 96 | public void checkServerTrusted( |
| 97 | X509Certificate[] certs, String authType) { |
| 98 | } |
| 99 | } |
| 100 | }; |
| 101 | |
Brian Carlstrom | 2c42c8f | 2010-09-14 00:11:14 -0700 | [diff] [blame] | 102 | sslContext.engineInit(null, trustManagers, null); |
| 103 | sslContext.engineGetClientSessionContext().setPersistentCache(cache); |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 104 | |
| 105 | synchronized (HttpsConnection.class) { |
| 106 | mSslSocketFactory = sslContext.engineGetSocketFactory(); |
| 107 | } |
| 108 | } catch (KeyManagementException e) { |
| 109 | throw new RuntimeException(e); |
| 110 | } catch (IOException e) { |
| 111 | throw new RuntimeException(e); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | private synchronized static SSLSocketFactory getSocketFactory() { |
| 116 | return mSslSocketFactory; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 117 | } |
| 118 | |
| 119 | /** |
| 120 | * Object to wait on when suspending the SSL connection |
| 121 | */ |
| 122 | private Object mSuspendLock = new Object(); |
| 123 | |
| 124 | /** |
| 125 | * True if the connection is suspended pending the result of asking the |
| 126 | * user about an error. |
| 127 | */ |
| 128 | private boolean mSuspended = false; |
| 129 | |
| 130 | /** |
| 131 | * True if the connection attempt should be aborted due to an ssl |
| 132 | * error. |
| 133 | */ |
| 134 | private boolean mAborted = false; |
| 135 | |
Patrick Scott | 86806ce1 | 2009-10-01 15:54:46 -0400 | [diff] [blame] | 136 | // Used when connecting through a proxy. |
| 137 | private HttpHost mProxyHost; |
| 138 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 139 | /** |
| 140 | * Contructor for a https connection. |
| 141 | */ |
Patrick Scott | 86806ce1 | 2009-10-01 15:54:46 -0400 | [diff] [blame] | 142 | HttpsConnection(Context context, HttpHost host, HttpHost proxy, |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 143 | RequestFeeder requestFeeder) { |
Patrick Scott | 86806ce1 | 2009-10-01 15:54:46 -0400 | [diff] [blame] | 144 | super(context, host, requestFeeder); |
| 145 | mProxyHost = proxy; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 146 | } |
| 147 | |
| 148 | /** |
| 149 | * Sets the server SSL certificate associated with this |
| 150 | * connection. |
| 151 | * @param certificate The SSL certificate |
| 152 | */ |
| 153 | /* package */ void setCertificate(SslCertificate certificate) { |
| 154 | mCertificate = certificate; |
| 155 | } |
| 156 | |
| 157 | /** |
| 158 | * Opens the connection to a http server or proxy. |
| 159 | * |
| 160 | * @return the opened low level connection |
| 161 | * @throws IOException if the connection fails for any reason. |
| 162 | */ |
| 163 | @Override |
| 164 | AndroidHttpClientConnection openConnection(Request req) throws IOException { |
| 165 | SSLSocket sslSock = null; |
| 166 | |
Patrick Scott | 86806ce1 | 2009-10-01 15:54:46 -0400 | [diff] [blame] | 167 | if (mProxyHost != null) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 168 | // If we have a proxy set, we first send a CONNECT request |
| 169 | // to the proxy; if the proxy returns 200 OK, we negotiate |
| 170 | // a secure connection to the target server via the proxy. |
| 171 | // If the request fails, we drop it, but provide the event |
| 172 | // handler with the response status and headers. The event |
| 173 | // handler is then responsible for cancelling the load or |
| 174 | // issueing a new request. |
| 175 | AndroidHttpClientConnection proxyConnection = null; |
| 176 | Socket proxySock = null; |
| 177 | try { |
| 178 | proxySock = new Socket |
Patrick Scott | 86806ce1 | 2009-10-01 15:54:46 -0400 | [diff] [blame] | 179 | (mProxyHost.getHostName(), mProxyHost.getPort()); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 180 | |
| 181 | proxySock.setSoTimeout(60 * 1000); |
| 182 | |
| 183 | proxyConnection = new AndroidHttpClientConnection(); |
| 184 | HttpParams params = new BasicHttpParams(); |
| 185 | HttpConnectionParams.setSocketBufferSize(params, 8192); |
| 186 | |
| 187 | proxyConnection.bind(proxySock, params); |
| 188 | } catch(IOException e) { |
| 189 | if (proxyConnection != null) { |
| 190 | proxyConnection.close(); |
| 191 | } |
| 192 | |
| 193 | String errorMessage = e.getMessage(); |
| 194 | if (errorMessage == null) { |
| 195 | errorMessage = |
| 196 | "failed to establish a connection to the proxy"; |
| 197 | } |
| 198 | |
| 199 | throw new IOException(errorMessage); |
| 200 | } |
| 201 | |
| 202 | StatusLine statusLine = null; |
| 203 | int statusCode = 0; |
| 204 | Headers headers = new Headers(); |
| 205 | try { |
| 206 | BasicHttpRequest proxyReq = new BasicHttpRequest |
| 207 | ("CONNECT", mHost.toHostString()); |
| 208 | |
Henrik Baard | ea4e597 | 2010-02-24 08:41:48 +0100 | [diff] [blame] | 209 | // add all 'proxy' headers from the original request, we also need |
| 210 | // to add 'host' header unless we want proxy to answer us with a |
| 211 | // 400 Bad Request |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 212 | for (Header h : req.mHttpRequest.getAllHeaders()) { |
Elliott Hughes | cb64d43 | 2013-08-02 10:00:44 -0700 | [diff] [blame] | 213 | String headerName = h.getName().toLowerCase(Locale.ROOT); |
Henrik Baard | ea4e597 | 2010-02-24 08:41:48 +0100 | [diff] [blame] | 214 | if (headerName.startsWith("proxy") || headerName.equals("keep-alive") |
| 215 | || headerName.equals("host")) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 216 | proxyReq.addHeader(h); |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | proxyConnection.sendRequestHeader(proxyReq); |
| 221 | proxyConnection.flush(); |
| 222 | |
| 223 | // it is possible to receive informational status |
| 224 | // codes prior to receiving actual headers; |
| 225 | // all those status codes are smaller than OK 200 |
| 226 | // a loop is a standard way of dealing with them |
| 227 | do { |
| 228 | statusLine = proxyConnection.parseResponseHeader(headers); |
| 229 | statusCode = statusLine.getStatusCode(); |
| 230 | } while (statusCode < HttpStatus.SC_OK); |
| 231 | } catch (ParseException e) { |
| 232 | String errorMessage = e.getMessage(); |
| 233 | if (errorMessage == null) { |
| 234 | errorMessage = |
| 235 | "failed to send a CONNECT request"; |
| 236 | } |
| 237 | |
| 238 | throw new IOException(errorMessage); |
| 239 | } catch (HttpException e) { |
| 240 | String errorMessage = e.getMessage(); |
| 241 | if (errorMessage == null) { |
| 242 | errorMessage = |
| 243 | "failed to send a CONNECT request"; |
| 244 | } |
| 245 | |
| 246 | throw new IOException(errorMessage); |
| 247 | } catch (IOException e) { |
| 248 | String errorMessage = e.getMessage(); |
| 249 | if (errorMessage == null) { |
| 250 | errorMessage = |
| 251 | "failed to send a CONNECT request"; |
| 252 | } |
| 253 | |
| 254 | throw new IOException(errorMessage); |
| 255 | } |
| 256 | |
| 257 | if (statusCode == HttpStatus.SC_OK) { |
| 258 | try { |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 259 | sslSock = (SSLSocket) getSocketFactory().createSocket( |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 260 | proxySock, mHost.getHostName(), mHost.getPort(), true); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 261 | } catch(IOException e) { |
| 262 | if (sslSock != null) { |
| 263 | sslSock.close(); |
| 264 | } |
| 265 | |
| 266 | String errorMessage = e.getMessage(); |
| 267 | if (errorMessage == null) { |
| 268 | errorMessage = |
| 269 | "failed to create an SSL socket"; |
| 270 | } |
| 271 | throw new IOException(errorMessage); |
| 272 | } |
| 273 | } else { |
| 274 | // if the code is not OK, inform the event handler |
| 275 | ProtocolVersion version = statusLine.getProtocolVersion(); |
| 276 | |
| 277 | req.mEventHandler.status(version.getMajor(), |
| 278 | version.getMinor(), |
| 279 | statusCode, |
| 280 | statusLine.getReasonPhrase()); |
| 281 | req.mEventHandler.headers(headers); |
| 282 | req.mEventHandler.endData(); |
| 283 | |
| 284 | proxyConnection.close(); |
| 285 | |
| 286 | // here, we return null to indicate that the original |
| 287 | // request needs to be dropped |
| 288 | return null; |
| 289 | } |
| 290 | } else { |
| 291 | // if we do not have a proxy, we simply connect to the host |
| 292 | try { |
Lorenzo Colitti | 79ae37086 | 2011-02-04 17:45:15 -0800 | [diff] [blame] | 293 | sslSock = (SSLSocket) getSocketFactory().createSocket( |
| 294 | mHost.getHostName(), mHost.getPort()); |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 295 | sslSock.setSoTimeout(SOCKET_TIMEOUT); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 296 | } catch(IOException e) { |
| 297 | if (sslSock != null) { |
| 298 | sslSock.close(); |
| 299 | } |
| 300 | |
| 301 | String errorMessage = e.getMessage(); |
| 302 | if (errorMessage == null) { |
| 303 | errorMessage = "failed to create an SSL socket"; |
| 304 | } |
| 305 | |
| 306 | throw new IOException(errorMessage); |
| 307 | } |
| 308 | } |
| 309 | |
| 310 | // do handshake and validate server certificates |
| 311 | SslError error = CertificateChainValidator.getInstance(). |
| 312 | doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName()); |
| 313 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 314 | // Inform the user if there is a problem |
| 315 | if (error != null) { |
| 316 | // handleSslErrorRequest may immediately unsuspend if it wants to |
| 317 | // allow the certificate anyway. |
| 318 | // So we mark the connection as suspended, call handleSslErrorRequest |
| 319 | // then check if we're still suspended and only wait if we actually |
| 320 | // need to. |
| 321 | synchronized (mSuspendLock) { |
| 322 | mSuspended = true; |
| 323 | } |
| 324 | // don't hold the lock while calling out to the event handler |
Brian Carlstrom | dba8cb7 | 2010-03-18 16:56:41 -0700 | [diff] [blame] | 325 | boolean canHandle = req.getEventHandler().handleSslErrorRequest(error); |
Grace Kloba | 4c8db8a | 2009-07-30 23:13:34 -0700 | [diff] [blame] | 326 | if(!canHandle) { |
| 327 | throw new IOException("failed to handle "+ error); |
| 328 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 329 | synchronized (mSuspendLock) { |
| 330 | if (mSuspended) { |
| 331 | try { |
| 332 | // Put a limit on how long we are waiting; if the timeout |
| 333 | // expires (which should never happen unless you choose |
| 334 | // to ignore the SSL error dialog for a very long time), |
| 335 | // we wake up the thread and abort the request. This is |
| 336 | // to prevent us from stalling the network if things go |
| 337 | // very bad. |
| 338 | mSuspendLock.wait(10 * 60 * 1000); |
| 339 | if (mSuspended) { |
| 340 | // mSuspended is true if we have not had a chance to |
| 341 | // restart the connection yet (ie, the wait timeout |
| 342 | // has expired) |
| 343 | mSuspended = false; |
| 344 | mAborted = true; |
| 345 | if (HttpLog.LOGV) { |
| 346 | HttpLog.v("HttpsConnection.openConnection():" + |
| 347 | " SSL timeout expired and request was cancelled!!!"); |
| 348 | } |
| 349 | } |
| 350 | } catch (InterruptedException e) { |
| 351 | // ignore |
| 352 | } |
| 353 | } |
| 354 | if (mAborted) { |
| 355 | // The user decided not to use this unverified connection |
| 356 | // so close it immediately. |
| 357 | sslSock.close(); |
| 358 | throw new SSLConnectionClosedByUserException("connection closed by the user"); |
| 359 | } |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | // All went well, we have an open, verified connection. |
| 364 | AndroidHttpClientConnection conn = new AndroidHttpClientConnection(); |
| 365 | BasicHttpParams params = new BasicHttpParams(); |
| 366 | params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192); |
| 367 | conn.bind(sslSock, params); |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 368 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 369 | return conn; |
| 370 | } |
| 371 | |
| 372 | /** |
| 373 | * Closes the low level connection. |
| 374 | * |
| 375 | * If an exception is thrown then it is assumed that the connection will |
| 376 | * have been closed (to the extent possible) anyway and the caller does not |
| 377 | * need to take any further action. |
| 378 | * |
| 379 | */ |
| 380 | @Override |
| 381 | void closeConnection() { |
| 382 | // if the connection has been suspended due to an SSL error |
| 383 | if (mSuspended) { |
| 384 | // wake up the network thread |
| 385 | restartConnection(false); |
| 386 | } |
| 387 | |
| 388 | try { |
| 389 | if (mHttpClientConnection != null && mHttpClientConnection.isOpen()) { |
| 390 | mHttpClientConnection.close(); |
| 391 | } |
| 392 | } catch (IOException e) { |
| 393 | if (HttpLog.LOGV) |
| 394 | HttpLog.v("HttpsConnection.closeConnection():" + |
| 395 | " failed closing connection " + mHost); |
| 396 | e.printStackTrace(); |
| 397 | } |
| 398 | } |
| 399 | |
| 400 | /** |
| 401 | * Restart a secure connection suspended waiting for user interaction. |
| 402 | */ |
| 403 | void restartConnection(boolean proceed) { |
| 404 | if (HttpLog.LOGV) { |
| 405 | HttpLog.v("HttpsConnection.restartConnection():" + |
| 406 | " proceed: " + proceed); |
| 407 | } |
| 408 | |
| 409 | synchronized (mSuspendLock) { |
| 410 | if (mSuspended) { |
| 411 | mSuspended = false; |
| 412 | mAborted = !proceed; |
| 413 | mSuspendLock.notify(); |
| 414 | } |
| 415 | } |
| 416 | } |
| 417 | |
| 418 | @Override |
| 419 | String getScheme() { |
| 420 | return "https"; |
| 421 | } |
| 422 | } |
Bob Lee | 886f3d6 | 2009-03-24 20:06:51 -0700 | [diff] [blame] | 423 | |
| 424 | /** |
| 425 | * Simple exception we throw if the SSL connection is closed by the user. |
| 426 | * |
| 427 | * {@hide} |
| 428 | */ |
| 429 | class SSLConnectionClosedByUserException extends SSLException { |
| 430 | |
| 431 | public SSLConnectionClosedByUserException(String reason) { |
| 432 | super(reason); |
| 433 | } |
| 434 | } |