How to create a custom secure socket connection between client/server? - java

I am developing a client/server application based on Java sockets and I would like some guide lines on how to make those secure/encrypted.
So, I have been reading the last two weeks about SSL, security, public and private key encryption, symmetric and asymmetric methodologies and I would like to implement a safe connection between client/server with the approach below:
Server: Create a public key for encryption and private key for decryption
Client: Uses Server's public key to encrypt his credentials along with a timestamp of request so as the encrypted value won't be the same every time he wants to authenticate himself. Attached to this message clients also includes the shared key that both client/server should connect (symmetric encryption).
Server: Decrypts the message with his private key, check user's credentials and timestamp. If credentials are correct, then it uses the symmetric key to send a response of success.
Rest of communication continues with client's symmetric key.
The below Java code is an example of how SSL communication can be achieved with SSL sockets. clientKeyStore.jks is the Client's Java Keystore which holds the server's public key. When a socket is created, I guess, that the client uses the public key to initiate the communication. My question is where the exchange of symmetric key happens, (if it happens?). Are there any vulnerabilities that I should have in mind, or is this safe from Man-In-The-Middle-Attack?
Thank you in advance
SSLSocketServer
public class SSLSocketServer {
private SSLServerSocket serverSocket;
public SSLSocketServer(int port) throws IOException {
System.setProperty("javax.net.ssl.keyStore","serverKeyStore.jks");
System.setProperty("javax.net.ssl.keyStorePassword", "serverKeyStorePass");
SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
serverSocket = (SSLServerSocket) factory.createServerSocket(port);
start();
}
public void start() {
while (true) {
try {
// Client is connected
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
System.out.println("User connected successfully?");
} // try
catch (IOException e) {
e.printStackTrace();
} // catch
} // while
} // run
} // class SSLSocketServer
SSLSocketClient
public class SSLSocketClient {
public SSLSocketClient(String serverHost, int serverPort) throws UnknownHostException, IOException {
System.setProperty("javax.net.ssl.trustStore","clientKeyStore.jks");
System.setProperty("javax.net.ssl.trustStorePassword", "clientKeyStorePass");
SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(serverHost, serverPort);
} // SocketClient()
} // class SSLSocketClient

Related

How can I preserve the order of my ciphersuite list in OkHttp?

I am unable to preserve the order of my ciphersuite list. I have set the order to be in my preferred way however, when the request is made and I check which order the ciphersuites are in, it is slightly different from my original. I am wondering if possibly okhttp is reordering them via a MAP structure or something else. If they are, I would like to learn where this is happening to be able to adjust it to preserve the order.
My connection spec used:
ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
.tlsVersions(TlsVersion.TLS_1_1, TlsVersion.TLS_1_2,TlsVersion.TLS_1_3)
.cipherSuites(
CipherSuite.TLS_AES_128_GCM_SHA256,
CipherSuite.TLS_AES_256_GCM_SHA384,
CipherSuite.TLS_CHACHA20_POLY1305_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
CipherSuite.TLS_RSA_WITH_AES_256_GCM_SHA384,
CipherSuite.TLS_RSA_WITH_AES_128_GCM_SHA256,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA256,
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA256,
CipherSuite.TLS_RSA_WITH_AES_256_CBC_SHA,
CipherSuite.TLS_RSA_WITH_AES_128_CBC_SHA,
CipherSuite.TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
CipherSuite.TLS_RSA_WITH_3DES_EDE_CBC_SHA
)
.build();
I want to also share that I am using conscrypt which enables these cipher suites. They are all used by okhttp but not in the correct order.
This looks like a bug in OkHttp https://github.com/square/okhttp/issues/6390
/**
* Returns an array containing only elements found in this array and also in [other]. The returned
* elements are in the same order as in this.
*/
fun Array<String>.intersect(
other: Array<String>,
comparator: Comparator<in String>
)
private fun supportedSpec(sslSocket: SSLSocket, isFallback: Boolean): ConnectionSpec {
var cipherSuitesIntersection = if (cipherSuitesAsString != null) {
sslSocket.enabledCipherSuites.intersect(cipherSuitesAsString, CipherSuite.ORDER_BY_NAME)
} else {
sslSocket.enabledCipherSuites
}
https://www.rfc-editor.org/rfc/rfc5246
The cipher suite list, passed from the client to the server in the
ClientHello message, contains the combinations of cryptographic
algorithms supported by the client in order of the client's
preference (favorite choice first). Each cipher suite defines a key
exchange algorithm, a bulk encryption algorithm (including secret key
length), a MAC algorithm, and a PRF. The server will select a cipher
suite or, if no acceptable choices are presented, return a handshake
failure alert and close the connection. If the list contains cipher
suites the server does not recognize, support, or wish to use, the
server MUST ignore those cipher suites, and process the remaining
ones as usual.
Yuri already mentioned that this is a bug within OkHttp. I am not quite sure how fast they will fix it but as an alternative you can still get it working with a delegate sslsocketfactory instead of using the ConnectionSpec
An example would setup would be:
Your SSL properties
SSLParameters sslParameters = new SSLParameters();
sslParameters.setProtocols(new String[]{"TLSv1.3", "TLSv1.2", "TLSv1.1"});
sslParameters.setCipherSuites(new String[]{
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA"
});
Your custom SSLSocketFactory
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public final class CompositeSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory sslSocketFactory;
private final SSLParameters sslParameters;
public CompositeSSLSocketFactory(SSLSocketFactory sslSocketFactory, SSLParameters sslParameters) {
this.sslSocketFactory = sslSocketFactory;
this.sslParameters = sslParameters;
}
#Override
public String[] getDefaultCipherSuites() {
return sslParameters.getCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return sslParameters.getCipherSuites();
}
#Override
public Socket createSocket() throws IOException {
Socket socket = sslSocketFactory.createSocket();
return withSslParameters(socket);
}
#Override
public Socket createSocket(Socket socket, InputStream inputStream, boolean autoClosable) throws IOException {
Socket newSocket = sslSocketFactory.createSocket(socket, inputStream, autoClosable);
return withSslParameters(newSocket);
}
#Override
public Socket createSocket(Socket socket, String host, int port, boolean autoClosable) throws IOException {
Socket newSocket = sslSocketFactory.createSocket(socket, host, port, autoClosable);
return withSslParameters(newSocket);
}
#Override
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
Socket socket = sslSocketFactory.createSocket(host, port);
return withSslParameters(socket);
}
#Override
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) throws IOException, UnknownHostException {
Socket socket = sslSocketFactory.createSocket(host, port, localAddress, localPort);
return withSslParameters(socket);
}
#Override
public Socket createSocket(InetAddress address, int port) throws IOException {
Socket socket = sslSocketFactory.createSocket(address, port);
return withSslParameters(socket);
}
#Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
Socket socket = sslSocketFactory.createSocket(address, port, localAddress, localPort);
return withSslParameters(socket);
}
private Socket withSslParameters(Socket socket) {
if (socket instanceof SSLSocket) {
SSLSocket sslSocket = (SSLSocket) socket;
sslSocket.setSSLParameters(sslParameters);
}
return socket;
}
}
Your OkHttp client configuration
SSLContext sslContext = ...; //your already initialised SSLContext
X509TrustManager trustManager = ...; //your already initialised TrustManager
SSLSocketFactory baseSocketFactory = sslContext.getSocketFactory();
SSLSocketFactory customSocketFactory = new CompositeSSLSocketFactory(baseSocketFactory, sslParameters);
OkHttpClient client = new OkHttpClient.Builder()
.sslSocketFactory(customSocketFactory, trustManager)
.build();
Yes it is verbose, however it will keep the order as you defined :) I would advise to wait for the bug-fix, but if you want to fix it by your self for the time being than this would be an option.

How to bypass Https SSLHandshakeException with Tomee

I am trying to invoke a WS over SSL, from a tomee 1.6 server, but I get a SSLHandshakeError. The problem is that the certificate is self signed, and is not recognized by my JVM. As it is only for test purpose, and not production, I have been asked to bypass the certificate control.
I read a lot of stuff about how to proceed, and I have written that code :
a class NaiveSSLContext :
package fr.csf.ssl;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Provider;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
/**
* A factory class which creates an {#link SSLContext} that
* naively accepts all certificates without verification.
*/
public class NaiveSSLContext
{
private NaiveSSLContext()
{}
/**
* Get an SSLContext that implements the specified secure
* socket protocol and naively accepts all certificates
* without verification.
*/
public static SSLContext getInstance( String protocol) throws NoSuchAlgorithmException
{
SSLContext sslCtx = SSLContext.getInstance( protocol);
init( sslCtx);
return sslCtx;
}
/**
* Get an SSLContext that implements the specified secure
* socket protocol and naively accepts all certificates
* without verification.
*/
public static SSLContext getInstance( String protocol, Provider provider) throws NoSuchAlgorithmException
{
SSLContext sslCtx = SSLContext.getInstance( protocol, provider);
init( sslCtx);
return sslCtx;
}
/**
* Get an SSLContext that implements the specified secure
* socket protocol and naively accepts all certificates
* without verification.
*/
public static SSLContext getInstance( String protocol, String provider) throws NoSuchAlgorithmException, NoSuchProviderException
{
SSLContext sslCtx = SSLContext.getInstance( protocol, provider);
init( sslCtx);
return sslCtx;
}
/**
* Set NaiveTrustManager to the given context.
*/
private static void init( SSLContext context)
{
try
{
// Set NaiveTrustManager.
context.init( null, new TrustManager[] { new NaiveTrustManager() }, new java.security.SecureRandom());
System.out.println( "------------- Initialisation du NaiveSSLContext ---------------------");
}
catch( java.security.KeyManagementException e)
{
throw new RuntimeException( "Failed to initialize an SSLContext.", e);
}
}
/**
* A {#link TrustManager} which trusts all certificates naively.
*/
private static class NaiveTrustManager implements X509TrustManager
{
#Override
public X509Certificate[] getAcceptedIssuers()
{
System.out.println( "------------- NaiveTrustManager.getAcceptedIssuers() ---------------------");
return null;
}
#Override
public void checkClientTrusted( X509Certificate[] certs, String authType)
{
System.out.println( "------------- NaiveTrustManager.checkClientTrusted( " + certs.toString() + ", " + authType
+ ") ---------------------");
}
#Override
public void checkServerTrusted( X509Certificate[] certs, String authType)
{
System.out.println( "------------- NaiveTrustManager.checkServerTrusted( " + certs.toString() + ", " + authType
+ ") ---------------------");
}
}
}
and another class NaiveSSLSocketFactory :
package fr.csf.ssl;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
public class NaiveSSLSocketFactory extends javax.net.ssl.SSLSocketFactory
{
private javax.net.ssl.SSLSocketFactory factory;
public NaiveSSLSocketFactory() throws NoSuchAlgorithmException
{
javax.net.ssl.SSLContext sslCtx = NaiveSSLContext.getInstance( "SSL");
factory = sslCtx.getSocketFactory();
}
private final String[] enabledProtocols = new String[]
{ "SSLv3", "TLSv1" };
#Override
public Socket createSocket( Socket s, String host, int port, boolean autoClose) throws IOException
{
Socket socket = factory.createSocket( s, host, port, autoClose);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public Socket createSocket( String host, int port) throws IOException, UnknownHostException
{
Socket socket = factory.createSocket( host, port);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public Socket createSocket( InetAddress host, int port) throws IOException
{
Socket socket = factory.createSocket( host, port);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public Socket createSocket( String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException
{
Socket socket = factory.createSocket( host, port, localHost, localPort);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public Socket createSocket( InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException
{
Socket socket = factory.createSocket( address, port, localAddress, localPort);
((javax.net.ssl.SSLSocket) socket).setEnabledProtocols( enabledProtocols);
return socket;
}
#Override
public String[] getDefaultCipherSuites()
{
String[] cipherSuites = factory.getDefaultCipherSuites();
return cipherSuites;
}
#Override
public String[] getSupportedCipherSuites()
{
String[] cipherSuites = factory.getSupportedCipherSuites();
return cipherSuites;
}
}
The problem is that I can't find out how to make the JVM use my Naive* classes instead of the default ones. I have tried different methods, but neither of them work :
First try :
javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory( new NaiveSSLSocketFactory());
My log traces in the checkClientTruted methods are never displayed. It seems that my NaiveSSLSocketFactory is never called.
2nd try :
java.security.Security.setProperty( "ssl.SocketFactory.provider", new NaiveSSLSocketFactory().getClass().getName());
I experienced a ClassNotFoundException due to a ClassLoader problem, but after this problem was fixed, the same problem remains.
I eventually found a blog where it was said that CXF client had to do a little more configuration stuff :
<http-conf:conduit name="*.http-conduit" >
<http-conf:tlsClientParameters
useHttpsURLConnectionDefaultSslSocketFactory="true"
/>
</http-conf:conduit>
As I use a Tomee1.6 server, my program is a CXF client. So that must be the solution. But where do I have to write this configuration properties ? I can't find any xml file in Tomee, related with CXF. There's only e cxf.properties file, which is nearly empty.
First, Tomcat isn't involved with your consumption of a web service - in fact it's really not involved with with any outbound connections your application is making.
I know of two ways to achieve your desired results provided by CXF in a way that won't affect any other outbound SSL connections running on the same JVM:
add the self-signed certificate to the CXF client's conduit trust
store, or
install a "do-nothing" trust manager to to the CXF
client's TLS parameters
The first method is preferable as the second will trust any endpoint your client connects with.
To implement the first method, create a key store containing the certificate you wish to trust (and for good measure, include any intermediary certificates). Then add this trust store as outlined in CXF handbook section Configuring SSL Support. Your conduit configuration will look something like this:
<http:conduit name="{http://apache.org/hello_world}HelloWorld.http-conduit">
<http:tlsClientParameters>
<sec:trustManagers>
<sec:keyStore type="JKS" password="password"
file="my/file/dir/Truststore.jks"/>
</sec:trustManagers>
</http:tlsClientParameters>
<http:client AutoRedirect="true" Connection="Keep-Alive"/>
</http:conduit>
Note that the conduit name in the example above is obviously just an example. See the update to my answer here regarding another question as how to specify the conduit name. Also note that I did not include a cipher suite filter as I believe it will default to some set of values, which is potentially unsafe if you're using Java 6 or older .. but that's a whole other topic.
Also, you can eschew Spring configuration of CXF entirely and do all of the above programmatically using CXF client APIs.
I also highly suggest using a tool like KeyStore Explorer to extract certificate (and intermediaries) from the target endpoint and import them into your new trust store.
Finally, I would like to point out, in reference to your initial solution, the danger of using JVM-wide installation of things like SSL socket factories and trust managers as supported by the JDK API. There is a possibility of perilous consequences of doing so when running inside of a container supporting multiple applications: you can subvert the security profile of other applications. One of the benefits of using a framework like CXF is that it provides means to customize SSL/TLS configurations for each application client (or server) instance.

Implementation of a manual SSL-handshake

I need some help with implementing a manual ssl handshake between an android smartphone and a server.
The background why is in my case the encryption/decryption of the communication. Android would be just a proxy which should have no information about what data is exchanged between the server and my javacard applet which only can communicate with the android device.
My first thought was to establich a connection to a server like a normal http just on the ssl port of the server: (in android)
Socket socket = new Socket("google.de", 443);
DataOutputStream os = new DataOutputStream(socket.getOutputStream());
DataInputStream is = new DataInputStream(socket.getInputStream());
// send data for the handshake
os.writeChars("Params for the SSL handshake");
// read the server response
in.read(...)
// answer with the next ssl handshake step
....
and then send the information for the handshake and wait for the response.
Problem here is, I don't know in which format I would have to send the parameters (like the client hello: protocolVersion, random, sessionID, cipherSuites, compressMethod). Or if it would work?
The other way I inspected was the
javax.net.ssl.SSLSocket;
which makes the handshake by itself.
For example:
public static void main(final String[] args) throws Exception {
final SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(null, null, null);
// getDefault();
final SSLSocketFactory fac = sslContext.getSocketFactory();
final SSLSocket socket = (SSLSocket) fac.createSocket("google.de", 443);
socket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
#Override
public void handshakeCompleted(final HandshakeCompletedEvent event) {
System.out.println("Cipher:" + event.getCipherSuite());
}
});
final String[] ciphers = fac.getSupportedCipherSuites();
final String[] protocols = { "TLSv1" };
final SSLParameters params = new SSLParameters(ciphers, protocols);
params.setNeedClientAuth(false);
socket.setSSLParameters(params);
socket.startHandshake();
}
In this case, all the relevant security information (like the secret exchange) and so on will happen on Android... (in detail: in the SSLSocket itself I think) That's exactly the way I don't want!
I want to have the possibility to send the handshake parameters by my own to the server and can catch the response from the server and will forward it to my javacard applet. I know I have to take care of the encryption later on by myself.
I hope my problem is understandable and the background why I need this too. Else please give a small hint and I will do my best to complete the information.
Thanks in advance!

Java SSL Socket Programming

I read about the SSLSocket when i had already finished a Chat program with java that use normal ServerSocket.
I am trying to replace the normal ServerSocket with SSlSocket, there is not much on the internet but i found something.
Now my WhServer class look like this:
This class is the one which start the Socket in a selected port, if you need to see other classes i will edit the question:
import java.io.IOException;
import java.net.*;
import javax.net.ServerSocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class WhServer extends Thread {
private int port;
private ServerSocket server;
private ChannelsManager manager;
SSLContext context;
SSLSocketFactory sslSf;
public WhServer(int port, ChannelsManager manager) throws IOException {
this.port = port;
this.manager = manager;
}
public void ServerStop() throws IOException{
server.close();
}
public WhServer(int port) throws IOException {
this(port, new ChannelsManager());
}
public int getPort() {
return port;
}
public void run() {
try {
while(true) {
ServerSocketFactory ssf = ServerSocketFactory.getDefault();
server = ssf.createServerSocket(port);
Socket socket = server.accept();
sslSf = context.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSf.createSocket(socket, null,socket.getPort(), false);
sslSocket.setUseClientMode(false);
manager.initialite(socket);
}
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
Firstly, your SSLContext context instance variable is never initialised, so it is null. Nothing specific to SSL or sockets here, it's just a basic Java error: if you try to call anything on this, it will throw an NPE.
Secondly, even if it's not null (for example, you can create a new instance with context = SSLContext.getInstance("TLS"), see SSLContext section of the Java Cryptography Architecture Standard Algorithm Name Documentation as indicated in the SSLContext API doc), you still need to initialise the SSLContext via its init method.
Since you're trying to implement a server, you'll need to provide a non-null keymanager, otherwise you'll get an SSLHandshakeException saying "no cipher suites in common". You can find details about this in this answer for example.
In addition, you don't need to use plain Sockets and upgrade them to SSLSockets after accepting like you do. It's not necessarily wrong, but the following might be easier:
// Assuming you've initialised your SSLContext
SSLServerSocketFactory sslSf = context.getServerSocketFactory();
SSLServerSocket server = (SSLServerSocket) sslSf.createServerSocket(port);
SSLServerSocketFactory ssf = ServerSocketFactory.getDefault();
server = ssf.createServerSocket(port);
SSLSocket socket = (SSLSocket)server.accept();
Your socket coming from an SSLServerSocketFactory will already be in server mode.
Of course, there's generally no need for the factories to be within the while loop.
context is null. nowhere in your code it is being initialized.
Here's a few static methods you can use to initialize it.
http://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLContext.html
static SSLContext getInstance(String protocol)
Returns a SSLContext object that implements the specified secure socket protocol.
static SSLContext getInstance(String protocol, Provider provider)
Returns a SSLContext object that implements the specified secure socket protocol.
static SSLContext getInstance(String protocol, String provider)
Returns a SSLContext object that implements the specified secure socket protocol.
Some valid values for the protocol string are "SSL", "SSLv2", "SSLv3"...
So, first of all, if you intend to keep that "context" variable as a member variable, make it final and initialize it in your constructor like this:
public WhServer(int port, ChannelsManager manager) throws IOException {
this.port = port;
this.manager = manager;
try {
context = SSLContext.getInstance("SSL"); //pick the SSL protocol you need.
} catch (Throwable t) { t.printStackTrace(); }
}
First you need to create SSLContext with below code:
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream("test.jks"),"passphrase".toCharArray());
// Create key manager
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, "passphrase".toCharArray());
KeyManager[] km = keyManagerFactory.getKeyManagers();
// Create trust manager
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("SunX509");
trustManagerFactory.init(keyStore);
TrustManager[] tm = trustManagerFactory.getTrustManagers();
// Initialize SSLContext
SSLContext sslContext = SSLContext.getInstance("TLSv1");
sslContext.init(km, tm, null);
Replace the test.jks with your own keystore location.
To understand the SSL communication model in Java, you can refer to Java Secure Socket Extension (JSSE) Reference Guide.
A HTTPS client and HTTPS server demo in Java provides a quite demo on how to create SSL client and SSL server in Java.

TLS connection using SSLSocket is slow in Android OS

I'm developing an Android app which uses SSLSocket to connect to a server. This is the code I'm using:
// Connect
if (socket == null || socket.isClosed() || !socket.isConnected()) {
if (socket != null && !socket.isClosed())
socket.close();
Log.i(getClass().toString(), "Connecting...");
if (sslContext == null) {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new SecureRandom());
}
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
socket = (SSLSocket)socketFactory.createSocket(host, port);
socket.setSoTimeout(20000);
socket.setUseClientMode(true);
connected = true;
Log.i(getClass().toString(), "Connected.");
}
// Secure
if (connected) {
Log.i(getClass().toString(), "Securing...");
SSLSession session = socket.getSession();
secured = session.isValid();
if (secured) {
Log.i(getClass().toString(), "Secured.");
}
else
Log.i(getClass().toString(), "Securing failed.");
}
The problem is that it takes about 5 seconds or event more to do the TLS handshake in the line below:
SSLSession session = socket.getSession();
I have made a similar iPhone app, the handshake takes just 1 second there, so I think the problem is not in the server I'm connecting to, it's maybe in the code above. The connection itself is fast enough, just the TLS handshake is slow.
Does anybody know if it's normal in Android, or if it is not, how to make it faster?
Thank you.
EDITED on 21.01.11:
I have found out, that the handshake is fast when I connect to another server, for example paypal.com:443.
But I had been connecting to another server before - a .NET service written by me. As I had said before, I did not think the problem was in that server because if I connect to it with my iPhone App the handshake is fast. Now I don't know why it is fast on iPhone and slow on Android. After the connection is established, the only thing I do in the .NET server is:
Console.WriteLine("New client connected.");
this.sslStream = new SslStream(tcpClient.GetStream(), true);
this.sslStream.ReadTimeout = 15000;
this.sslStream.WriteTimeout = 15000;
Console.WriteLine("Beginning TLS handshake...");
this.sslStream.AuthenticateAsServer(connection.ServerCertificate, false, SslProtocols.Tls, false);
Console.WriteLine("TLS handshake completed.");
There was a bug on earlier versions of the Android SDK. Apparently, it's doing an unnecessary DNS reverse lookup. You need to prevent this from happening. Here's a workaround that worked for me. It used to take 15 seconds, now it takes 0-1 seconds. Hope it helps.
Here's the link to the Google issue.
boolean connected = false;
if (socket == null || socket.isClosed() || !socket.isConnected()) {
if (socket != null && !socket.isClosed()) {
socket.close();
}
Log.i(getClass().toString(), "Connecting...");
messages.getText().append("Connecting...");
final KeyStore keyStore = KeyStore.getInstance("BKS");
keyStore.load(getResources().openRawResource(R.raw.serverkey), null);
final KeyManagerFactory keyManager = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManager.init(keyStore, null);
//keyManager.init(null, null);
final TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(keyStore);
sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyManager.getKeyManagers(), trustFactory.getTrustManagers(), rnd);
final SSLSocketFactory delegate = sslContext.getSocketFactory();
SocketFactory factory = new SSLSocketFactory() {
#Override
public Socket createSocket(String host, int port)
throws IOException, UnknownHostException {
InetAddress addr = InetAddress.getByName(host);
injectHostname(addr, host);
return delegate.createSocket(addr, port);
}
#Override
public Socket createSocket(InetAddress host, int port)
throws IOException {
return delegate.createSocket(host, port);
}
#Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort)
throws IOException, UnknownHostException {
return delegate.createSocket(host, port, localHost, localPort);
}
#Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort)
throws IOException {
return delegate.createSocket(address, port, localAddress, localPort);
}
private void injectHostname(InetAddress address, String host) {
try {
Field field = InetAddress.class.getDeclaredField("hostName");
field.setAccessible(true);
field.set(address, host);
} catch (Exception ignored) {
}
}
#Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
injectHostname(s.getInetAddress(), host);
return delegate.createSocket(s, host, port, autoClose);
}
#Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
};
socket = (SSLSocket)factory.createSocket("192.168.197.133", 9999);
socket.setSoTimeout(20000);
socket.setUseClientMode(true);
connected = true;
Log.i(getClass().toString(), "Connected.");
messages.getText().append("Connected.");
}
// Secure
if (connected) {
Log.i(getClass().toString(), "Securing...");
messages.getText().append("Securing...");
SSLSession session = socket.getSession();
boolean secured = session.isValid();
if (secured) {
Log.i(getClass().toString(), "Secured.");
messages.getText().append("Secured.");
}
}
You are using a new SecureRandom per connection, instead of using a single static pre-initialized SecureRandom. Everytime you create a new SecureRandom(), you need to gather entropy for seeding (a slow process).
SecureRandom does not self-seed until it is first used, which is why the delay does not occur until the call to getSession()
I have done something similar to this and it is slower than an unsecured connection. Granted my case was https vs http and it is a little different the SSL/TLS factor will add slowness to the deal.
I have two identical apps that comunicate with the same protocol to the same server, one in android and one in iPhone, both using https. When I tested them both in http I would see more or less the same response time, in https iOS was slightly faster in my case, but not terribly.
The problem is most likely in the way the device validates server certificates. Validation can involve contacting third-party for CRLs and OCSP responses. If this happens, it takes time. iPhone probably just doesn't do this (at least by default) which is a security hole BTW.

Categories