1
0
mirror of https://github.com/owncloud/android-library.git synced 2025-06-27 17:56:10 +00:00
2014-01-20 13:51:09 +01:00

297 lines
12 KiB
Java

/* ownCloud Android Library is available under MIT license
* Copyright (C) 2014 ownCloud (http://www.owncloud.org/)
* Copyright (C) 2012 Bartek Przybylski
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
package com.owncloud.android.lib.network;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
//import java.security.Provider;
import java.security.cert.X509Certificate;
//import java.util.Enumeration;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
//import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
//import android.os.Build;
import android.util.Log;
/**
* AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with
* a custom SSLContext and an optional Hostname Verifier.
*
* @author David A. Velasco
*/
public class AdvancedSslSocketFactory implements ProtocolSocketFactory {
private static final String TAG = AdvancedSslSocketFactory.class.getSimpleName();
private SSLContext mSslContext = null;
private AdvancedX509TrustManager mTrustManager = null;
private X509HostnameVerifier mHostnameVerifier = null;
public SSLContext getSslContext() {
return mSslContext;
}
/**
* Constructor for AdvancedSSLProtocolSocketFactory.
*/
public AdvancedSslSocketFactory(SSLContext sslContext, AdvancedX509TrustManager trustManager, X509HostnameVerifier hostnameVerifier) {
if (sslContext == null)
throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null SSLContext");
if (trustManager == null)
throw new IllegalArgumentException("AdvancedSslSocketFactory can not be created with a null Trust Manager");
mSslContext = sslContext;
mTrustManager = trustManager;
mHostnameVerifier = hostnameVerifier;
}
/**
* @see ProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
*/
public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) throws IOException, UnknownHostException {
Socket socket = mSslContext.getSocketFactory().createSocket(host, port, clientHost, clientPort);
verifyPeerIdentity(host, port, socket);
return socket;
}
/*
private void logSslInfo() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) {
Log.v(TAG, "SUPPORTED SSL PARAMETERS");
logSslParameters(mSslContext.getSupportedSSLParameters());
Log.v(TAG, "DEFAULT SSL PARAMETERS");
logSslParameters(mSslContext.getDefaultSSLParameters());
Log.i(TAG, "CURRENT PARAMETERS");
Log.i(TAG, "Protocol: " + mSslContext.getProtocol());
}
Log.i(TAG, "PROVIDER");
logSecurityProvider(mSslContext.getProvider());
}
private void logSecurityProvider(Provider provider) {
Log.i(TAG, "name: " + provider.getName());
Log.i(TAG, "version: " + provider.getVersion());
Log.i(TAG, "info: " + provider.getInfo());
Enumeration<?> keys = provider.propertyNames();
String key;
while (keys.hasMoreElements()) {
key = (String) keys.nextElement();
Log.i(TAG, " property " + key + " : " + provider.getProperty(key));
}
}
private void logSslParameters(SSLParameters params) {
Log.v(TAG, "Cipher suites: ");
String [] elements = params.getCipherSuites();
for (int i=0; i<elements.length ; i++) {
Log.v(TAG, " " + elements[i]);
}
Log.v(TAG, "Protocols: ");
elements = params.getProtocols();
for (int i=0; i<elements.length ; i++) {
Log.v(TAG, " " + elements[i]);
}
}
*/
/**
* Attempts to get a new socket connection to the given host within the
* given time limit.
*
* @param host the host name/IP
* @param port the port on the host
* @param clientHost the local host name/IP to bind the socket to
* @param clientPort the port on the local machine
* @param params {@link HttpConnectionParams Http connection parameters}
*
* @return Socket a new socket
*
* @throws IOException if an I/O error occurs while creating the socket
* @throws UnknownHostException if the IP address of the host cannot be
* determined
*/
public Socket createSocket(final String host, final int port,
final InetAddress localAddress, final int localPort,
final HttpConnectionParams params) throws IOException,
UnknownHostException, ConnectTimeoutException {
Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port + ", local " + localAddress + ":" + localPort + ", params: " + params);
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
//logSslInfo();
SocketFactory socketfactory = mSslContext.getSocketFactory();
Log.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout());
Socket socket = socketfactory.createSocket();
SocketAddress localaddr = new InetSocketAddress(localAddress, localPort);
SocketAddress remoteaddr = new InetSocketAddress(host, port);
socket.setSoTimeout(params.getSoTimeout());
socket.bind(localaddr);
ServerNameIndicator.setServerNameIndication(host, (SSLSocket)socket);
socket.connect(remoteaddr, timeout);
verifyPeerIdentity(host, port, socket);
return socket;
}
/**
* @see ProtocolSocketFactory#createSocket(java.lang.String,int)
*/
public Socket createSocket(String host, int port) throws IOException,
UnknownHostException {
Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port);
Socket socket = mSslContext.getSocketFactory().createSocket(host, port);
verifyPeerIdentity(host, port, socket);
return socket;
}
public boolean equals(Object obj) {
return ((obj != null) && obj.getClass().equals(
AdvancedSslSocketFactory.class));
}
public int hashCode() {
return AdvancedSslSocketFactory.class.hashCode();
}
public X509HostnameVerifier getHostNameVerifier() {
return mHostnameVerifier;
}
public void setHostNameVerifier(X509HostnameVerifier hostnameVerifier) {
mHostnameVerifier = hostnameVerifier;
}
/**
* Verifies the identity of the server.
*
* The server certificate is verified first.
*
* Then, the host name is compared with the content of the server certificate using the current host name verifier, if any.
* @param socket
*/
private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException {
try {
CertificateCombinedException failInHandshake = null;
/// 1. VERIFY THE SERVER CERTIFICATE through the registered TrustManager (that should be an instance of AdvancedX509TrustManager)
try {
SSLSocket sock = (SSLSocket) socket; // a new SSLSession instance is created as a "side effect"
sock.startHandshake();
} catch (RuntimeException e) {
if (e instanceof CertificateCombinedException) {
failInHandshake = (CertificateCombinedException) e;
} else {
Throwable cause = e.getCause();
Throwable previousCause = null;
while (cause != null && cause != previousCause && !(cause instanceof CertificateCombinedException)) {
previousCause = cause;
cause = cause.getCause();
}
if (cause != null && cause instanceof CertificateCombinedException) {
failInHandshake = (CertificateCombinedException)cause;
}
}
if (failInHandshake == null) {
throw e;
}
failInHandshake.setHostInUrl(host);
}
/// 2. VERIFY HOSTNAME
SSLSession newSession = null;
boolean verifiedHostname = true;
if (mHostnameVerifier != null) {
if (failInHandshake != null) {
/// 2.1 : a new SSLSession instance was NOT created in the handshake
X509Certificate serverCert = failInHandshake.getServerCertificate();
try {
mHostnameVerifier.verify(host, serverCert);
} catch (SSLException e) {
verifiedHostname = false;
}
} else {
/// 2.2 : a new SSLSession instance was created in the handshake
newSession = ((SSLSocket)socket).getSession();
if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) {
verifiedHostname = mHostnameVerifier.verify(host, newSession);
}
}
}
/// 3. Combine the exceptions to throw, if any
if (!verifiedHostname) {
SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException("Names in the server certificate do not match to " + host + " in the URL");
if (failInHandshake == null) {
failInHandshake = new CertificateCombinedException((X509Certificate) newSession.getPeerCertificates()[0]);
failInHandshake.setHostInUrl(host);
}
failInHandshake.setSslPeerUnverifiedException(pue);
pue.initCause(failInHandshake);
throw pue;
} else if (failInHandshake != null) {
SSLHandshakeException hse = new SSLHandshakeException("Server certificate could not be verified");
hse.initCause(failInHandshake);
throw hse;
}
} catch (IOException io) {
try {
socket.close();
} catch (Exception x) {
// NOTHING - irrelevant exception for the caller
}
throw io;
}
}
}