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