1 /* ownCloud Android Library is available under MIT license
2 * Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
3 * Copyright (C) 2012 Bartek Przybylski
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:
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
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
26 package com
.owncloud
.android
.oc_framework
.network
;
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;
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
;
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
;
52 //import android.os.Build;
53 import android
.util
.Log
;
58 * AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with
59 * a custom SSLContext and an optional Hostname Verifier.
61 * @author David A. Velasco
64 public class AdvancedSslSocketFactory
implements ProtocolSocketFactory
{
66 private static final String TAG
= AdvancedSslSocketFactory
.class.getSimpleName();
68 private SSLContext mSslContext
= null
;
69 private AdvancedX509TrustManager mTrustManager
= null
;
70 private X509HostnameVerifier mHostnameVerifier
= null
;
72 public SSLContext
getSslContext() {
77 * Constructor for AdvancedSSLProtocolSocketFactory.
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
;
90 * @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
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
);
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());
108 Log.i(TAG, "PROVIDER");
109 logSecurityProvider(mSslContext.getProvider());
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();
118 while (keys.hasMoreElements()) {
119 key = (String) keys.nextElement();
120 Log.i(TAG, " property " + key + " : " + provider.getProperty(key));
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]);
130 Log.v(TAG, "Protocols: ");
131 elements = params.getProtocols();
132 for (int i=0; i<elements.length ; i++) {
133 Log.v(TAG, " " + elements[i]);
139 * Attempts to get a new socket connection to the given host within the
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}
148 * @return Socket a new socket
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
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");
162 int timeout
= params
.getConnectionTimeout();
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
);
180 * @see ProtocolSocketFactory#createSocket(java.lang.String,int)
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
);
190 public boolean equals(Object obj
) {
191 return ((obj
!= null
) && obj
.getClass().equals(
192 AdvancedSslSocketFactory
.class));
195 public int hashCode() {
196 return AdvancedSslSocketFactory
.class.hashCode();
200 public X509HostnameVerifier
getHostNameVerifier() {
201 return mHostnameVerifier
;
205 public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier
) {
206 mHostnameVerifier
= hostnameVerifier
;
210 * Verifies the identity of the server.
212 * The server certificate is verified first.
214 * Then, the host name is compared with the content of the server certificate using the current host name verifier, if any.
217 private void verifyPeerIdentity(String host
, int port
, Socket socket
) throws IOException
{
219 CertificateCombinedException failInHandshake
= null
;
220 /// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager)
222 SSLSocket sock
= (SSLSocket
) socket
; // a new SSLSession instance is created as a "side effect"
223 sock
.startHandshake();
225 } catch (RuntimeException e
) {
227 if (e
instanceof CertificateCombinedException
) {
228 failInHandshake
= (CertificateCombinedException
) e
;
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();
236 if (cause
!= null
&& cause
instanceof CertificateCombinedException
) {
237 failInHandshake
= (CertificateCombinedException
)cause
;
240 if (failInHandshake
== null
) {
243 failInHandshake
.setHostInUrl(host
);
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();
255 mHostnameVerifier
.verify(host
, serverCert
);
256 } catch (SSLException e
) {
257 verifiedHostname
= false
;
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
);
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
);
276 failInHandshake
.setSslPeerUnverifiedException(pue
);
277 pue
.initCause(failInHandshake
);
280 } else if (failInHandshake
!= null
) {
281 SSLHandshakeException hse
= new SSLHandshakeException("Server certificate could not be verified");
282 hse
.initCause(failInHandshake
);
286 } catch (IOException io
) {
289 } catch (Exception x
) {
290 // NOTHING - irrelevant exception for the caller