blob: 84765a5cbcd2ba6422359721a943702446b70a35 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.net.http;
18
19import android.content.Context;
Bob Lee886f3d62009-03-24 20:06:51 -070020import android.util.Log;
21import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache;
Brian Carlstrom3c7c3512010-08-04 15:44:39 -070022import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl;
Bob Lee886f3d62009-03-24 20:06:51 -070023import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache;
Bob Lee886f3d62009-03-24 20:06:51 -070024import org.apache.http.Header;
25import org.apache.http.HttpException;
26import org.apache.http.HttpHost;
27import org.apache.http.HttpStatus;
28import org.apache.http.ParseException;
29import org.apache.http.ProtocolVersion;
30import org.apache.http.StatusLine;
31import org.apache.http.message.BasicHttpRequest;
32import org.apache.http.params.BasicHttpParams;
33import org.apache.http.params.HttpConnectionParams;
34import org.apache.http.params.HttpParams;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080036import javax.net.ssl.SSLException;
37import javax.net.ssl.SSLSocket;
38import javax.net.ssl.SSLSocketFactory;
39import javax.net.ssl.TrustManager;
40import javax.net.ssl.X509TrustManager;
Bob Lee886f3d62009-03-24 20:06:51 -070041import java.io.File;
42import java.io.IOException;
43import java.net.InetSocketAddress;
44import java.net.Socket;
45import java.security.KeyManagementException;
46import java.security.cert.X509Certificate;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080047
48/**
49 * A Connection connecting to a secure http server or tunneling through
50 * a http proxy server to a https server.
Bob Lee886f3d62009-03-24 20:06:51 -070051 *
52 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053 */
Bob Lee886f3d62009-03-24 20:06:51 -070054public class HttpsConnection extends Connection {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055
56 /**
57 * SSL socket factory
58 */
59 private static SSLSocketFactory mSslSocketFactory = null;
60
61 static {
Brian Carlstrom37254dc2010-03-02 13:30:04 -080062 // This initialization happens in the zygote. It triggers some
Bob Lee886f3d62009-03-24 20:06:51 -070063 // lazy initialization that can will benefit later invocations of
64 // initializeEngine().
65 initializeEngine(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066 }
67
68 /**
Bob Lee886f3d62009-03-24 20:06:51 -070069 * @hide
70 *
71 * @param sessionDir directory to cache SSL sessions
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080072 */
Bob Lee886f3d62009-03-24 20:06:51 -070073 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 Carlstrom3c7c3512010-08-04 15:44:39 -070082 OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
Bob Lee886f3d62009-03-24 20:06:51 -070083
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 Carlstrom2c42c8f2010-09-14 00:11:14 -0700101 sslContext.engineInit(null, trustManagers, null);
102 sslContext.engineGetClientSessionContext().setPersistentCache(cache);
Bob Lee886f3d62009-03-24 20:06:51 -0700103
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 Project9066cfe2009-03-03 19:31:44 -0800116 }
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 Scott86806ce12009-10-01 15:54:46 -0400135 // Used when connecting through a proxy.
136 private HttpHost mProxyHost;
137
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138 /**
139 * Contructor for a https connection.
140 */
Patrick Scott86806ce12009-10-01 15:54:46 -0400141 HttpsConnection(Context context, HttpHost host, HttpHost proxy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800142 RequestFeeder requestFeeder) {
Patrick Scott86806ce12009-10-01 15:54:46 -0400143 super(context, host, requestFeeder);
144 mProxyHost = proxy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800145 }
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 Scott86806ce12009-10-01 15:54:46 -0400166 if (mProxyHost != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 // 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 Scott86806ce12009-10-01 15:54:46 -0400178 (mProxyHost.getHostName(), mProxyHost.getPort());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179
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 Baardea4e5972010-02-24 08:41:48 +0100208 // 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 Project9066cfe2009-03-03 19:31:44 -0800211 for (Header h : req.mHttpRequest.getAllHeaders()) {
212 String headerName = h.getName().toLowerCase();
Henrik Baardea4e5972010-02-24 08:41:48 +0100213 if (headerName.startsWith("proxy") || headerName.equals("keep-alive")
214 || headerName.equals("host")) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800215 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 Lee886f3d62009-03-24 20:06:51 -0700258 sslSock = (SSLSocket) getSocketFactory().createSocket(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800259 proxySock, mHost.getHostName(), mHost.getPort(), true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 } 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 Colitti79ae3702011-02-04 17:45:15 -0800292 sslSock = (SSLSocket) getSocketFactory().createSocket(
293 mHost.getHostName(), mHost.getPort());
Bob Lee886f3d62009-03-24 20:06:51 -0700294 sslSock.setSoTimeout(SOCKET_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800295 } 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 Project9066cfe2009-03-03 19:31:44 -0800313 // 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 Carlstromdba8cb72010-03-18 16:56:41 -0700324 boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
Grace Kloba4c8db8a2009-07-30 23:13:34 -0700325 if(!canHandle) {
326 throw new IOException("failed to handle "+ error);
327 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 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 Lee886f3d62009-03-24 20:06:51 -0700367
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 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 Lee886f3d62009-03-24 20:06:51 -0700422
423/**
424 * Simple exception we throw if the SSL connection is closed by the user.
425 *
426 * {@hide}
427 */
428class SSLConnectionClosedByUserException extends SSLException {
429
430 public SSLConnectionClosedByUserException(String reason) {
431 super(reason);
432 }
433}