1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 * Copyright (C) 2012-2013 ownCloud Inc.
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.
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.
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/>.
19 package com
.owncloud
.android
.oc_framework
.network
;
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;
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
;
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
;
45 //import android.os.Build;
46 import android
.util
.Log
;
51 * AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with
52 * a custom SSLContext and an optional Hostname Verifier.
54 * @author David A. Velasco
57 public class AdvancedSslSocketFactory
implements ProtocolSocketFactory
{
59 private static final String TAG
= AdvancedSslSocketFactory
.class.getSimpleName();
61 private SSLContext mSslContext
= null
;
62 private AdvancedX509TrustManager mTrustManager
= null
;
63 private X509HostnameVerifier mHostnameVerifier
= null
;
65 public SSLContext
getSslContext() {
70 * Constructor for AdvancedSSLProtocolSocketFactory.
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
;
83 * @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
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
);
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());
101 Log.i(TAG, "PROVIDER");
102 logSecurityProvider(mSslContext.getProvider());
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();
111 while (keys.hasMoreElements()) {
112 key = (String) keys.nextElement();
113 Log.i(TAG, " property " + key + " : " + provider.getProperty(key));
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]);
123 Log.v(TAG, "Protocols: ");
124 elements = params.getProtocols();
125 for (int i=0; i<elements.length ; i++) {
126 Log.v(TAG, " " + elements[i]);
132 * Attempts to get a new socket connection to the given host within the
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}
141 * @return Socket a new socket
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
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");
155 int timeout
= params
.getConnectionTimeout();
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
);
173 * @see ProtocolSocketFactory#createSocket(java.lang.String,int)
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
);
183 public boolean equals(Object obj
) {
184 return ((obj
!= null
) && obj
.getClass().equals(
185 AdvancedSslSocketFactory
.class));
188 public int hashCode() {
189 return AdvancedSslSocketFactory
.class.hashCode();
193 public X509HostnameVerifier
getHostNameVerifier() {
194 return mHostnameVerifier
;
198 public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier
) {
199 mHostnameVerifier
= hostnameVerifier
;
203 * Verifies the identity of the server.
205 * The server certificate is verified first.
207 * Then, the host name is compared with the content of the server certificate using the current host name verifier, if any.
210 private void verifyPeerIdentity(String host
, int port
, Socket socket
) throws IOException
{
212 CertificateCombinedException failInHandshake
= null
;
213 /// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager)
215 SSLSocket sock
= (SSLSocket
) socket
; // a new SSLSession instance is created as a "side effect"
216 sock
.startHandshake();
218 } catch (RuntimeException e
) {
220 if (e
instanceof CertificateCombinedException
) {
221 failInHandshake
= (CertificateCombinedException
) e
;
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();
229 if (cause
!= null
&& cause
instanceof CertificateCombinedException
) {
230 failInHandshake
= (CertificateCombinedException
)cause
;
233 if (failInHandshake
== null
) {
236 failInHandshake
.setHostInUrl(host
);
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();
248 mHostnameVerifier
.verify(host
, serverCert
);
249 } catch (SSLException e
) {
250 verifiedHostname
= false
;
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
);
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
);
269 failInHandshake
.setSslPeerUnverifiedException(pue
);
270 pue
.initCause(failInHandshake
);
273 } else if (failInHandshake
!= null
) {
274 SSLHandshakeException hse
= new SSLHandshakeException("Server certificate could not be verified");
275 hse
.initCause(failInHandshake
);
279 } catch (IOException io
) {
282 } catch (Exception x
) {
283 // NOTHING - irrelevant exception for the caller