SSLDroid/src/hu/blint/ssldroid/TcpProxyServerThread.java

185 lines
7.7 KiB
Java

package hu.blint.ssldroid;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import android.util.Log;
public class TcpProxyServerThread extends Thread {
String tunnelName;
int listenPort;
String tunnelHost;
int tunnelPort;
String keyFile, keyPass;
Relay inRelay, outRelay;
ServerSocket ss = null;
int sessionid = 0;
private SSLSocketFactory sslSocketFactory;
public TcpProxyServerThread(String tunnelName, int listenPort, String tunnelHost, int tunnelPort, String keyFile, String keyPass) {
this.tunnelName = tunnelName;
this.listenPort = listenPort;
this.tunnelHost = tunnelHost;
this.tunnelPort = tunnelPort;
this.keyFile = keyFile;
this.keyPass = keyPass;
}
// Create a trust manager that does not validate certificate chains
// TODO: handle this somehow properly (popup if cert is untrusted?)
// TODO: cacert + crl should be configurable
TrustManager[] trustAllCerts = new TrustManager[] {
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
public final SSLSocketFactory getSocketFactory(String pkcsFile,
String pwd, int sessionid) {
if (sslSocketFactory == null) {
try {
KeyManagerFactory keyManagerFactory;
if (pkcsFile != null && !pkcsFile.isEmpty()) {
keyManagerFactory = KeyManagerFactory.getInstance("X509");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream(pkcsFile), pwd.toCharArray());
keyManagerFactory.init(keyStore, pwd.toCharArray());
} else {
keyManagerFactory = null;
}
SSLContext context = SSLContext.getInstance("TLS");
context.init(keyManagerFactory == null ? null : keyManagerFactory.getKeyManagers(), trustAllCerts,
new SecureRandom());
sslSocketFactory = context.getSocketFactory();
} catch (FileNotFoundException e) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": Error loading the client certificate file:"
+ e.toString());
} catch (KeyManagementException e) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": No SSL algorithm support: " + e.toString());
} catch (NoSuchAlgorithmException e) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": No common SSL algorithm found: " + e.toString());
} catch (KeyStoreException e) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": Error setting up keystore:" + e.toString());
} catch (java.security.cert.CertificateException e) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": Error loading the client certificate:" + e.toString());
} catch (IOException e) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": Error loading the client certificate file:" + e.toString());
} catch (UnrecoverableKeyException e) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": Error loading the client certificate:" + e.toString());
}
}
return sslSocketFactory;
}
public void run() {
try {
ss = new ServerSocket(listenPort, 50, InetAddress.getLocalHost());
Log.d("SSLDroid", "Listening for connections on "+InetAddress.getLocalHost().getHostAddress()+":"+
+ this.listenPort + " ...");
} catch (Exception e) {
Log.d("SSLDroid", "Error setting up listening socket: " + e.toString());
return;
}
while (true) {
try {
Thread fromBrowserToServer = null;
Thread fromServerToBrowser = null;
if (isInterrupted()) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": Interrupted server thread, closing sockets...");
ss.close();
return;
}
// accept the connection from my client
Socket sc = null;
try {
sc = ss.accept();
sessionid++;
} catch (SocketException e) {
Log.d("SSLDroid", "Accept failure: " + e.toString());
}
Socket st = null;
try {
final SSLSocketFactory sf = getSocketFactory(this.keyFile, this.keyPass, this.sessionid);
st = (SSLSocket) sf.createSocket(this.tunnelHost, this.tunnelPort);
setSNIHost(sf, (SSLSocket) st, this.tunnelHost);
((SSLSocket) st).startHandshake();
} catch (IOException e) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": SSL failure: " + e.toString());
return;
}
catch (Exception e) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": SSL failure: " + e.toString());
if (sc != null)
{
sc.close();
}
return;
}
if (sc == null || st == null) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": Trying socket operation on a null socket, returning");
return;
}
Log.d("SSLDroid", tunnelName+"/"+sessionid+": Tunnelling port "
+ listenPort + " to port "
+ tunnelPort + " on host "
+ tunnelHost + " ...");
// relay the stuff through
fromBrowserToServer = new Relay(
this, sc.getInputStream(), st.getOutputStream(), "client", sessionid);
fromServerToBrowser = new Relay(
this, st.getInputStream(), sc.getOutputStream(), "server", sessionid);
fromBrowserToServer.start();
fromServerToBrowser.start();
} catch (IOException ee) {
Log.d("SSLDroid", tunnelName+"/"+sessionid+": Ouch: " + ee.toString());
}
}
}
private void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) {
if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
((android.net.SSLCertificateSocketFactory)factory).setHostname(socket, hostname);
} else {
try {
socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname);
} catch (Throwable e) {
// ignore any error, we just can't set the hostname...
}
}
}
};