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