Merge pull request #118 from owncloud/loggingtool
[pub/Android/ownCloud.git] / src / com / owncloud / android / network / AdvancedSslSocketFactory.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 * Copyright (C) 2012-2013 ownCloud Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20 package com.owncloud.android.network;
21
22 import java.io.IOException;
23 import java.net.InetAddress;
24 import java.net.InetSocketAddress;
25 import java.net.Socket;
26 import java.net.SocketAddress;
27 import java.net.UnknownHostException;
28 import java.security.cert.X509Certificate;
29
30 import javax.net.SocketFactory;
31 import javax.net.ssl.SSLContext;
32 import javax.net.ssl.SSLException;
33 import javax.net.ssl.SSLHandshakeException;
34 import javax.net.ssl.SSLPeerUnverifiedException;
35 import javax.net.ssl.SSLSession;
36 import javax.net.ssl.SSLSocket;
37
38 import org.apache.commons.httpclient.ConnectTimeoutException;
39 import org.apache.commons.httpclient.params.HttpConnectionParams;
40 import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
41 import org.apache.http.conn.ssl.X509HostnameVerifier;
42
43 import com.owncloud.android.Log_OC;
44
45 import android.util.Log;
46
47 /**
48 * AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with
49 * a custom SSLContext and an optional Hostname Verifier.
50 *
51 * @author David A. Velasco
52 */
53
54 public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
55
56 private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName();
57
58 private SSLContext mSslContext = null;
59 private AdvancedX509TrustManager mTrustManager = null;
60 private X509HostnameVerifier mHostnameVerifier = null;
61
62 public SSLContext getSslContext() {
63 return mSslContext;
64 }
65
66 /**
67 * Constructor for AdvancedSSLProtocolSocketFactory.
68 */
69 public AdvancedSslSocketFactory(SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier) {
70 if (sslContext == null)
71 throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext");
72 if (trustManager == null)
73 throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null Trust Manager");
74 mSslContext = sslContext;
75 mTrustManager = trustManager;
76 mHostnameVerifier = hostnameVerifier;
77 }
78
79 /**
80 * @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
81 */
82 public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException {
83 Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort);
84 verifyPeerIdentity(host, port, socket);
85 return socket;
86 }
87
88
89 /**
90 * Attempts to get a new socket connection to the given host within the
91 * given time limit.
92 *
93 * @param host the host name/IP
94 * @param port the port on the host
95 * @param clientHost the local host name/IP to bind the socket to
96 * @param clientPort the port on the local machine
97 * @param params {@link HttpConnectionParams Http connection parameters}
98 *
99 * @return Socket a new socket
100 *
101 * @throws IOException if an I/O error occurs while creating the socket
102 * @throws UnknownHostException if the IP address of the host cannot be
103 * determined
104 */
105 public Socket createSocket(final String host, final int port,
106 final InetAddress localAddress, final int localPort,
107 final HttpConnectionParams params) throws IOException,
108 UnknownHostException, ConnectTimeoutException {
109 Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + localPort + ", params: " + params);
110 if (params == null) {
111 throw new IllegalArgumentException("Parameters may not be null");
112 }
113 int timeout = params.getConnectionTimeout();
114 SocketFactory socketfactory = mSslContext.getSocketFactory();
115 Log_OC.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout());
116 Socket socket = socketfactory.createSocket();
117 SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
118 SocketAddress remoteaddr = new InetSocketAddress(host, port);
119 socket.setSoTimeout(params.getSoTimeout());
120 socket.bind(localaddr);
121 socket.connect(remoteaddr, timeout);
122 verifyPeerIdentity(host, port, socket);
123 return socket;
124 }
125
126 /**
127 * @see ProtocolSocketFactory#createSocket(java.lang.String,int)
128 */
129 public Socket createSocket(String host, int port) throws IOException,
130 UnknownHostException {
131 Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port);
132 Socket socket = mSslContext.getSocketFactory().createSocket(host, port);
133 verifyPeerIdentity(host, port, socket);
134 return socket;
135 }
136
137 public boolean equals(Object obj) {
138 return ((obj != null) && obj.getClass().equals(
139 AdvancedSslSocketFactory.class));
140 }
141
142 public int hashCode() {
143 return AdvancedSslSocketFactory.class.hashCode();
144 }
145
146
147 public X509HostnameVerifier getHostNameVerifier() {
148 return mHostnameVerifier;
149 }
150
151
152 public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier) {
153 mHostnameVerifier = hostnameVerifier;
154 }
155
156 /**
157 * Verifies the identity of the server.
158 *
159 * The server certificate is verified first.
160 *
161 * Then, the host name is compared with the content of the server certificate using the current host name verifier, if any.
162 * @param socket
163 */
164 private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException {
165 try {
166 CertificateCombinedException failInHandshake = null;
167 /// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager)
168 try {
169 SSLSocket sock = (SSLSocket) socket; // a new SSLSession instance is created as a "side effect"
170 sock.startHandshake();
171
172 } catch (RuntimeException e) {
173
174 if (e instanceof CertificateCombinedException) {
175 failInHandshake = (CertificateCombinedException) e;
176 } else {
177 Throwable cause = e.getCause();
178 Throwable previousCause = null;
179 while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) {
180 previousCause = cause;
181 cause = cause.getCause();
182 }
183 if (cause != null && cause instanceof CertificateCombinedException) {
184 failInHandshake = (CertificateCombinedException)cause;
185 }
186 }
187 if (failInHandshake == null) {
188 throw e;
189 }
190 failInHandshake.setHostInUrl(host);
191
192 }
193
194 /// 2. VERIFY HOSTNAME
195 SSLSession newSession = null;
196 boolean verifiedHostname = true;
197 if (mHostnameVerifier != null) {
198 if (failInHandshake != null) {
199 /// 2.1 : a new SSLSession instance was NOT created in the handshake
200 X509Certificate serverCert = failInHandshake.getServerCertificate();
201 try {
202 mHostnameVerifier.verify(host, serverCert);
203 } catch (SSLException e) {
204 verifiedHostname = false;
205 }
206
207 } else {
208 /// 2.2 : a new SSLSession instance was created in the handshake
209 newSession = ((SSLSocket)socket).getSession();
210 if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) {
211 verifiedHostname = mHostnameVerifier.verify(host, newSession);
212 }
213 }
214 }
215
216 /// 3. Combine the exceptions to throw, if any
217 if (!verifiedHostname) {
218 SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException("Names in the server certificate do not match to " + host + " in the URL");
219 if (failInHandshake == null) {
220 failInHandshake = new CertificateCombinedException((X509Certificate) newSession.getPeerCertificates()[0]);
221 failInHandshake.setHostInUrl(host);
222 }
223 failInHandshake.setSslPeerUnverifiedException(pue);
224 pue.initCause(failInHandshake);
225 throw pue;
226
227 } else if (failInHandshake != null) {
228 SSLHandshakeException hse = new SSLHandshakeException("Server certificate could not be verified");
229 hse.initCause(failInHandshake);
230 throw hse;
231 }
232
233 } catch (IOException io) {
234 try {
235 socket.close();
236 } catch (Exception x) {
237 // NOTHING - irrelevant exception for the caller
238 }
239 throw io;
240 }
241 }
242
243 }