196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project/* 296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Copyright (C) 2008 The Android Open Source Project 396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * 496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * you may not use this file except in compliance with the License. 696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * You may obtain a copy of the License at 796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * 896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * 1096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 1196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 1296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * See the License for the specific language governing permissions and 1496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * limitations under the License. 1596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 1696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 1796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectpackage com.android.email.mail.transport; 1896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 19b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blankimport android.content.Context; 20b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank 2151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdonimport com.android.email.DebugUtils; 2231d9acbf0623872f9d4a2b3210b5970854b654c7Marc Blankimport com.android.emailcommon.Logging; 232193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.CertificateValidationException; 242193962ca2b3157e79f731736afa2a0c972e778aMarc Blankimport com.android.emailcommon.mail.MessagingException; 257d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blankimport com.android.emailcommon.provider.HostAuth; 263a5c1fb274a9ce72d708d88509bf2607cb018dddMarc Blankimport com.android.emailcommon.utility.SSLUtils; 2793a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdonimport com.android.mail.analytics.Analytics; 28560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedyimport com.android.mail.utils.LogUtils; 2996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 3096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.BufferedInputStream; 3196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.BufferedOutputStream; 3296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.IOException; 3396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.InputStream; 3496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.io.OutputStream; 35f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onukiimport java.net.InetAddress; 3696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.InetSocketAddress; 3796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.Socket; 3896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.SocketAddress; 3996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport java.net.SocketException; 4096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 41fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.HostnameVerifier; 42fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.HttpsURLConnection; 4396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Projectimport javax.net.ssl.SSLException; 44fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLPeerUnverifiedException; 45fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLSession; 46fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadlerimport javax.net.ssl.SSLSocket; 4796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 48b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blankpublic class MailTransport { 498d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy 5096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project // TODO protected eventually 5196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /*protected*/ public static final int SOCKET_CONNECT_TIMEOUT = 10000; 5296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /*protected*/ public static final int SOCKET_READ_TIMEOUT = 60000; 5396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 54fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler private static final HostnameVerifier HOSTNAME_VERIFIER = 55fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler HttpsURLConnection.getDefaultHostnameVerifier(); 56fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler 577d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank private final String mDebugLabel; 587d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank private final Context mContext; 5917d3a29c9d8f7a27c463239f190bdcc4e0804527Jerry Xie protected final HostAuth mHostAuth; 6096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 6196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project private Socket mSocket; 6296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project private InputStream mIn; 6396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project private OutputStream mOut; 6496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 657d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank public MailTransport(Context context, String debugLabel, HostAuth hostAuth) { 6696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project super(); 677d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank mContext = context; 6896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mDebugLabel = debugLabel; 697d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank mHostAuth = hostAuth; 7096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 718d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy 727d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank /** 73ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy * Returns a new transport, using the current transport as a model. The new transport is 74ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy * configured identically (as if {@link #setSecurity(int, boolean)}, {@link #setPort(int)} 75ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy * and {@link #setHost(String)} were invoked), but not opened or connected in any way. 7696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 77ebece4dbdcdfee85a410a0d00c9b6739ee3e705eTodd Kennedy @Override 78b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank public MailTransport clone() { 797d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank return new MailTransport(mContext, mDebugLabel, mHostAuth); 80a50fc99b0c433f0cde31ba1c7ab87fb9ea86345dTodd Kennedy } 81a50fc99b0c433f0cde31ba1c7ab87fb9ea86345dTodd Kennedy 8296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public String getHost() { 837d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank return mHostAuth.mAddress; 8496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 8596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 8696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public int getPort() { 877d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank return mHostAuth.mPort; 8896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 8996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 9096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public boolean canTrySslSecurity() { 917d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank return (mHostAuth.mFlags & HostAuth.FLAG_SSL) != 0; 9296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 938d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy 9496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public boolean canTryTlsSecurity() { 957d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank return (mHostAuth.mFlags & HostAuth.FLAG_TLS) != 0; 9696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 978d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy 98e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler public boolean canTrustAllCertificates() { 997d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank return (mHostAuth.mFlags & HostAuth.FLAG_TRUST_ALL) != 0; 100e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler } 101e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler 10296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /** 10396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Attempts to open a connection using the Uri supplied for connection parameters. Will attempt 10496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * an SSL connection if indicated. 10596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 10696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public void open() throws MessagingException, CertificateValidationException { 10751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG) { 108560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "*** " + mDebugLabel + " open " + 10996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project getHost() + ":" + String.valueOf(getPort())); 11096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 11196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 11296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project try { 11396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project SocketAddress socketAddress = new InetSocketAddress(getHost(), getPort()); 11496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (canTrySslSecurity()) { 1157d5e2a7c08966ffd4a9e8c78f504cc4fd5be4216Marc Blank mSocket = SSLUtils.getSSLSocketFactory( 116601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon mContext, mHostAuth, null, canTrustAllCertificates()).createSocket(); 11796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } else { 11896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mSocket = new Socket(); 11996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 12096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); 121fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler // After the socket connects to an SSL server, confirm that the hostname is as expected 122fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler if (canTrySslSecurity() && !canTrustAllCertificates()) { 123fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler verifyHostname(mSocket, getHost()); 124fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler } 125f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon Analytics.getInstance().sendEvent("socket_certificates", 126f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon "open", Boolean.toString(canTrustAllCertificates()), 0); 12793a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon if (mSocket instanceof SSLSocket) { 12893a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon final SSLSocket sslSocket = (SSLSocket) mSocket; 12993a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon if (sslSocket.getSession() != null) { 130f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon Analytics.getInstance().sendEvent("cipher_suite", 131f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon sslSocket.getSession().getProtocol(), 13293a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon sslSocket.getSession().getCipherSuite(), 0); 13393a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon } 13493a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon } 13596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mIn = new BufferedInputStream(mSocket.getInputStream(), 1024); 13696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512); 1379a2686afa2e02c62a8e5f9c42c82bd2da70b96afYu Ping Hu mSocket.setSoTimeout(SOCKET_READ_TIMEOUT); 13896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } catch (SSLException e) { 13951c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG) { 140560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, e.toString()); 14196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 14296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project throw new CertificateValidationException(e.getMessage(), e); 14396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } catch (IOException ioe) { 14451c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG) { 145560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, ioe.toString()); 14696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 14796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project throw new MessagingException(MessagingException.IOERROR, ioe.toString()); 148532bcf4a522f91258c74604b74096c9b1f7c0510Paul Westbrook } catch (IllegalArgumentException iae) { 14951c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG) { 150560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, iae.toString()); 151532bcf4a522f91258c74604b74096c9b1f7c0510Paul Westbrook } 152532bcf4a522f91258c74604b74096c9b1f7c0510Paul Westbrook throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION, iae.toString()); 15396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 15496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 15596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 15696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /** 15796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Attempts to reopen a TLS connection using the Uri supplied for connection parameters. 15896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * 159fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * NOTE: No explicit hostname verification is required here, because it's handled automatically 160fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * by the call to createSocket(). 161fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * 16296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * TODO should we explicitly close the old socket? This seems funky to abandon it. 16396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 16496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public void reopenTls() throws MessagingException { 16596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project try { 166601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon mSocket = SSLUtils.getSSLSocketFactory(mContext, mHostAuth, null, 167601700a61e453c612e0dabe4e93002766b3751b7Martin Hibdon canTrustAllCertificates()) 168c5912e4920bb4fa1979d63d47e7f430e87e3820fMakoto Onuki .createSocket(mSocket, getHost(), getPort(), true); 16996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mSocket.setSoTimeout(SOCKET_READ_TIMEOUT); 17096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mIn = new BufferedInputStream(mSocket.getInputStream(), 1024); 17196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512); 17296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 173f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon Analytics.getInstance().sendEvent("socket_certificates", 174f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon "reopenTls", Boolean.toString(canTrustAllCertificates()), 0); 17593a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon final SSLSocket sslSocket = (SSLSocket) mSocket; 17693a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon if (sslSocket.getSession() != null) { 177f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon Analytics.getInstance().sendEvent("cipher_suite", 178f0bbcd85eaba6624d4c52150e83930e816d873a4Martin Hibdon sslSocket.getSession().getProtocol(), 17993a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon sslSocket.getSession().getCipherSuite(), 0); 18093a9662d8db14e492da0cf4866265a0ddebda190Martin Hibdon } 181e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler } catch (SSLException e) { 18251c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG) { 183560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, e.toString()); 184e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler } 185e4a7cc440f081ef9c4375a2bd2f82680cc11b152Andrew Stadler throw new CertificateValidationException(e.getMessage(), e); 18696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } catch (IOException ioe) { 18751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG) { 188560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, ioe.toString()); 18996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 19096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project throw new MessagingException(MessagingException.IOERROR, ioe.toString()); 19196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 19296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 193fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler 194fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler /** 195fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this 196fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * service but is not in the public API. 197fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * 198fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * Verify the hostname of the certificate used by the other end of a 199fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * connected socket. You MUST call this if you did not supply a hostname 200fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * to SSLCertificateSocketFactory.createSocket(). It is harmless to call this method 201fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * redundantly if the hostname has already been verified. 202fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * 203fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * <p>Wildcard certificates are allowed to verify any matching hostname, 204fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * so "foo.bar.example.com" is verified if the peer has a certificate 205fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * for "*.example.com". 206fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * 207fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * @param socket An SSL socket which has been connected to a server 208fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * @param hostname The expected hostname of the remote server 209fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * @throws IOException if something goes wrong handshaking with the server 210fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler * @throws SSLPeerUnverifiedException if the server cannot prove its identity 211fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler */ 2121b8e0fa23f6e9957f0b8753dd3f5b95d3f5d98eaScott Kennedy private static void verifyHostname(Socket socket, String hostname) throws IOException { 213fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler // The code at the start of OpenSSLSocketImpl.startHandshake() 214fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler // ensures that the call is idempotent, so we can safely call it. 215fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler SSLSocket ssl = (SSLSocket) socket; 216fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler ssl.startHandshake(); 217fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler 218fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler SSLSession session = ssl.getSession(); 219fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler if (session == null) { 220fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler throw new SSLException("Cannot verify SSL socket without session"); 221fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler } 222fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler // TODO: Instead of reporting the name of the server we think we're connecting to, 223fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler // we should be reporting the bad name in the certificate. Unfortunately this is buried 224fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler // in the verifier code and is not available in the verifier API, and extracting the 225fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler // CN & alts is beyond the scope of this patch. 226fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler if (!HOSTNAME_VERIFIER.verify(hostname, session)) { 227fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler throw new SSLPeerUnverifiedException( 228fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler "Certificate hostname not useable for server: " + hostname); 229fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler } 230fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler } 231fb060de65db57607748cbf8bc5b93939281a443fAndrew Stadler 23296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /** 2330c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler * Get the socket timeout. 2340c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler * @return the read timeout value in milliseconds 2350c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler * @throws SocketException 2360c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler */ 2370c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler public int getSoTimeout() throws SocketException { 2380c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler return mSocket.getSoTimeout(); 2390c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler } 2400c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler 2410c8696c2ebd52c7f2a92fa7b6b8d5d2005c19d1cTony Mantler /** 24296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Set the socket timeout. 24396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * @param timeoutMilliseconds the read timeout value if greater than {@code 0}, or 24496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * {@code 0} for an infinite timeout. 24596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 24696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public void setSoTimeout(int timeoutMilliseconds) throws SocketException { 24796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mSocket.setSoTimeout(timeoutMilliseconds); 24896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 24996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 25096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public boolean isOpen() { 2518d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy return (mIn != null && mOut != null && 25296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mSocket != null && mSocket.isConnected() && !mSocket.isClosed()); 25396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 25496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 25596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /** 25696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Close the connection. MUST NOT return any exceptions - must be "best effort" and safe. 25796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 25896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public void close() { 25996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project try { 26096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mIn.close(); 26196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } catch (Exception e) { 26296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project // May fail if the connection is already closed. 26396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 26496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project try { 26596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mOut.close(); 26696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } catch (Exception e) { 26796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project // May fail if the connection is already closed. 26896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 26996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project try { 27096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mSocket.close(); 27196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } catch (Exception e) { 27296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project // May fail if the connection is already closed. 27396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 27496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mIn = null; 27596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mOut = null; 27696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project mSocket = null; 27796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 27896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 27996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public InputStream getInputStream() { 28096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return mIn; 28196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 28296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 28396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public OutputStream getOutputStream() { 28496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return mOut; 28596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 2868d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy 28796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /** 28896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Writes a single line to the server using \r\n termination. 28996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 29096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project public void writeLine(String s, String sensitiveReplacement) throws IOException { 29151c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (DebugUtils.DEBUG) { 292bfac9f2e8a13f6c719608a6948203bbef921c99fMakoto Onuki if (sensitiveReplacement != null && !Logging.DEBUG_SENSITIVE) { 293560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, ">>> " + sensitiveReplacement); 29496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } else { 295560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, ">>> " + s); 29696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 29796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 2988d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy 29996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project OutputStream out = getOutputStream(); 30096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project out.write(s.getBytes()); 30196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project out.write('\r'); 30296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project out.write('\n'); 30396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project out.flush(); 30496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 3058d8537cd2e39268e0fdcd019bc8b6c4572b7c520Todd Kennedy 30696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project /** 30796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * Reads a single line from the server, using either \r\n or \n as the delimiter. The 30896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project * delimiter char(s) are not included in the result. 30996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project */ 310b203b2b1196bfd5507c83a4fe81d362de840ec0aMarc Blank public String readLine(boolean loggable) throws IOException { 31196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project StringBuffer sb = new StringBuffer(); 31296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project InputStream in = getInputStream(); 31396c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project int d; 31496c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project while ((d = in.read()) != -1) { 31596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project if (((char)d) == '\r') { 31696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project continue; 31796c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } else if (((char)d) == '\n') { 31896c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project break; 31996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } else { 32096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project sb.append((char)d); 32196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 32296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 32351c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (d == -1 && DebugUtils.DEBUG) { 324560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "End of stream reached while trying to read line."); 32596c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 32696c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project String ret = sb.toString(); 32751c653646d14d841fbe527aee9fab7a1886338f8Martin Hibdon if (loggable && DebugUtils.DEBUG) { 328560bfadc3151f7a06f3b06e9a6c92cfa534c63ecScott Kennedy LogUtils.d(Logging.LOG_TAG, "<<< " + ret); 32996c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 33096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project return ret; 33196c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project } 33296c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project 333f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki public InetAddress getLocalAddress() { 334f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki if (isOpen()) { 335f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki return mSocket.getLocalAddress(); 336f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki } else { 337f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki return null; 338f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki } 339f4dac9f266906a84f4710d8af5d4a24f2290b1baMakoto Onuki } 34096c5af40d639d629267794f4f0338a267ff94ce5The Android Open Source Project} 341