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