/* ownCloud Android client application
* Copyright (C) 2012 Bartek Przybylski
+ * Copyright (C) 2012-2013 ownCloud Inc.
*
* This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
-import java.security.cert.Certificate;
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.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
-import javax.net.ssl.X509TrustManager;
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.util.Log;
+import com.owncloud.android.Log_OC;
/**
* AdvancedSSLProtocolSocketFactory allows to create SSL {@link Socket}s with
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);
+ Log_OC.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();
SocketFactory socketfactory = mSslContext.getSocketFactory();
- Log.d(TAG, " ... with connection timeout " + timeout + " and socket timeout " + params.getSoTimeout());
+ Log_OC.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);
*/
public Socket createSocket(String host, int port) throws IOException,
UnknownHostException {
- Log.d(TAG, "Creating SSL Socket with remote " + host + ":" + port);
+ Log_OC.d(TAG, "Creating SSL Socket with remote " + host + ":" + port);
Socket socket = mSslContext.getSocketFactory().createSocket(host, port);
verifyPeerIdentity(host, port, socket);
return socket;
*/
private void verifyPeerIdentity(String host, int port, Socket socket) throws IOException {
try {
- IOException failInHandshake = null;
+ 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 (IOException e) {
- failInHandshake = e;
- if (!(e.getCause() instanceof CertificateCombinedException)) {
+
+ } 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
if (mHostnameVerifier != null) {
if (failInHandshake != null) {
/// 2.1 : a new SSLSession instance was NOT created in the handshake
- X509Certificate serverCert = ((CertificateCombinedException)failInHandshake.getCause()).getServerCertificate();
+ X509Certificate serverCert = failInHandshake.getServerCertificate();
try {
mHostnameVerifier.verify(host, serverCert);
} catch (SSLException e) {
} else {
/// 2.2 : a new SSLSession instance was created in the handshake
- if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
- /// this is sure ONLY for Android >= 3.0 ; the same is true for mHostnameVerifier.verify(host, (SSLSocket)socket)
- newSession = ((SSLSocket)socket).getSession();
- if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0])))
- verifiedHostname = mHostnameVerifier.verify(host, newSession);
-
- } else {
- //// performing the previous verification in Android versions under 2.3.x (and we don't know the exact value of x) WILL BREAK THE SSL CONTEXT, and any HTTP operation executed later through the socket WILL FAIL ;
- //// it is related with A BUG IN THE OpenSSLSOcketImpl.java IN THE ANDROID CORE SYSTEM; it was fixed here:
- //// http://gitorious.org/ginger/libcore/blobs/df349b3eaf4d1fa0643ab722173bc3bf20a266f5/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java
- /// but we could not find out in what Android version was released the bug fix;
- ///
- /// besides, due to the bug, calling ((SSLSocket)socket).getSession() IS NOT SAFE ; the next workaround is an UGLY BUT SAFE solution to get it
- SSLSessionContext sessionContext = mSslContext.getClientSessionContext();
- if (sessionContext != null) {
- SSLSession session = null;
- synchronized(sessionContext) { // a SSLSession in the SSLSessionContext can be closed while we are searching for the new one; it happens; really
- Enumeration<byte[]> ids = sessionContext.getIds();
- while (ids.hasMoreElements()) {
- session = sessionContext.getSession(ids.nextElement());
- if ( session.getPeerHost().equals(host) &&
- session.getPeerPort() == port &&
- (newSession == null || newSession.getCreationTime() < session.getCreationTime())) {
- newSession = session;
- }
- }
- }
- if (newSession != null) {
- if (!mTrustManager.isKnownServer((X509Certificate)(newSession.getPeerCertificates()[0]))) {
- verifiedHostname = mHostnameVerifier.verify(host, newSession);
- }
- } else {
- Log.d(TAG, "Hostname verification could not be performed because the new SSLSession was not found");
- }
- }
+ 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 (failInHandshake != null) {
- if (!verifiedHostname) {
- ((CertificateCombinedException)failInHandshake.getCause()).setSslPeerUnverifiedException(new SSLPeerUnverifiedException(host));
+ 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);
}
- throw failInHandshake;
- } else if (!verifiedHostname) {
- CertificateCombinedException ce = new CertificateCombinedException((X509Certificate) newSession.getPeerCertificates()[0]);
- SSLPeerUnverifiedException pue = new SSLPeerUnverifiedException(host);
- ce.setSslPeerUnverifiedException(pue);
- pue.initCause(ce);
+ 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) {