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