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