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