blob: 7a12e539c965e0ac6900b1b3da05cba2a026e370 [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;
Kenny Root12e75222013-04-23 22:34:24 -070021import com.android.org.conscrypt.FileClientSessionCache;
22import com.android.org.conscrypt.OpenSSLContextImpl;
23import com.android.org.conscrypt.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;
Elliott Hughescb64d432013-08-02 10:00:44 -070047import java.util.Locale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048
49/**
50 * A Connection connecting to a secure http server or tunneling through
51 * a http proxy server to a https server.
Bob Lee886f3d62009-03-24 20:06:51 -070052 *
53 * @hide
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 */
Bob Lee886f3d62009-03-24 20:06:51 -070055public class HttpsConnection extends Connection {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056
57 /**
58 * SSL socket factory
59 */
60 private static SSLSocketFactory mSslSocketFactory = null;
61
62 static {
Brian Carlstrom37254dc2010-03-02 13:30:04 -080063 // This initialization happens in the zygote. It triggers some
Bob Lee886f3d62009-03-24 20:06:51 -070064 // lazy initialization that can will benefit later invocations of
65 // initializeEngine().
66 initializeEngine(null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067 }
68
69 /**
Bob Lee886f3d62009-03-24 20:06:51 -070070 * @hide
71 *
72 * @param sessionDir directory to cache SSL sessions
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073 */
Bob Lee886f3d62009-03-24 20:06:51 -070074 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 Carlstrom3c7c3512010-08-04 15:44:39 -070083 OpenSSLContextImpl sslContext = new OpenSSLContextImpl();
Bob Lee886f3d62009-03-24 20:06:51 -070084
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 Carlstrom2c42c8f2010-09-14 00:11:14 -0700102 sslContext.engineInit(null, trustManagers, null);
103 sslContext.engineGetClientSessionContext().setPersistentCache(cache);
Bob Lee886f3d62009-03-24 20:06:51 -0700104
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 Project9066cfe2009-03-03 19:31:44 -0800117 }
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 Scott86806ce12009-10-01 15:54:46 -0400136 // Used when connecting through a proxy.
137 private HttpHost mProxyHost;
138
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 /**
140 * Contructor for a https connection.
141 */
Patrick Scott86806ce12009-10-01 15:54:46 -0400142 HttpsConnection(Context context, HttpHost host, HttpHost proxy,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800143 RequestFeeder requestFeeder) {
Patrick Scott86806ce12009-10-01 15:54:46 -0400144 super(context, host, requestFeeder);
145 mProxyHost = proxy;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 }
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 Scott86806ce12009-10-01 15:54:46 -0400167 if (mProxyHost != null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 // 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 Scott86806ce12009-10-01 15:54:46 -0400179 (mProxyHost.getHostName(), mProxyHost.getPort());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800180
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 Baardea4e5972010-02-24 08:41:48 +0100209 // 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 Project9066cfe2009-03-03 19:31:44 -0800212 for (Header h : req.mHttpRequest.getAllHeaders()) {
Elliott Hughescb64d432013-08-02 10:00:44 -0700213 String headerName = h.getName().toLowerCase(Locale.ROOT);
Henrik Baardea4e5972010-02-24 08:41:48 +0100214 if (headerName.startsWith("proxy") || headerName.equals("keep-alive")
215 || headerName.equals("host")) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800216 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 Lee886f3d62009-03-24 20:06:51 -0700259 sslSock = (SSLSocket) getSocketFactory().createSocket(
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800260 proxySock, mHost.getHostName(), mHost.getPort(), true);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 } 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 Colitti79ae3702011-02-04 17:45:15 -0800293 sslSock = (SSLSocket) getSocketFactory().createSocket(
294 mHost.getHostName(), mHost.getPort());
Bob Lee886f3d62009-03-24 20:06:51 -0700295 sslSock.setSoTimeout(SOCKET_TIMEOUT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 } 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 Project9066cfe2009-03-03 19:31:44 -0800314 // 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 Carlstromdba8cb72010-03-18 16:56:41 -0700325 boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
Grace Kloba4c8db8a2009-07-30 23:13:34 -0700326 if(!canHandle) {
327 throw new IOException("failed to handle "+ error);
328 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 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 Lee886f3d62009-03-24 20:06:51 -0700368
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 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 Lee886f3d62009-03-24 20:06:51 -0700423
424/**
425 * Simple exception we throw if the SSL connection is closed by the user.
426 *
427 * {@hide}
428 */
429class SSLConnectionClosedByUserException extends SSLException {
430
431 public SSLConnectionClosedByUserException(String reason) {
432 super(reason);
433 }
434}