I am stumped as to how my client program is able to create an SSLSocket and connect to a server program successfully even though the keystore file (sessionKeyStore below) is null.
The following code will successfully create a TLSv1.2 socket with a null keystore and successfully handshake with my server program which I thought always requires a key exchange.
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(sessionKeyStore); // sessionKeyStore IS NULL!!
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(sessionKeyStore, SSL_PASSWORD.toCharArray()); // password is not null
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
SecureRandom sc = createSecureRandom();
sc.setSeed(System.nanoTime());
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), sc);
SSLSocketFactory factory = sc.getSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket(server, serverPort);
((SSLSocket) socket).startHandshake();
I didn't realize that client authentication is not included in the basic TLS handshake. Only the server is authenticated. Client authentication is a separate handshake that occurs after the server is first authenticated and only if the server requests it. The problem was I needed to specifically tell the Server Socket to demand that the client certificate verification be included in the TLS handshake. The way to do that in Java JSSE is by adding the following code to the server side:
SSLContext sslContext = getSSLContext();
SSLServerSocketFactory sslserversocketfactory = sslContext.getServerSocketFactory();
SSLServerSocket sslserversocket = (SSLServerSocket) sslserversocketfactory.createServerSocket(port);
// Add the below to force a certificate request on the client.
sslserversocket.setNeedClientAuth(true);
https://www.comparitech.com/blog/information-security/tls-encryption
Related
I am trying to implement secured Socket connections between a Spring Server and an Android Client but I found some design problems.
At the beginning I implemented the solution provided in this tutorial (it's in Spanish but easily understandable with Google Traductor). This tutorial secures Socket connections with a key in each side (one for server and one for client) and uses a trustedKeys.jks to store trusted keys in each side.
That means that for every client I should:
Create a new keystore for each new client
Add this new client key to trusted keys on server side
Add server keystore to each new client
This seemed unrealistic to me considering the growth of the number of clients.
I found another approach that suits better my requirements, it uses the server certificate as a public key (I think) and ciphers the data with it, that will be deciphered by the server keystore:
Server Code
public SSLServerSocket getSSLServerSocket(int port) throws IOException, ...
{
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(resourceKeyFile.getInputStream(), keystorePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keystore, keystorePassword.toCharArray());
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(kmf.getKeyManagers(), null, null);
SSLServerSocketFactory sslServerSocketfactory = sc.getServerSocketFactory();
return (SSLServerSocket) sslServerSocketfactory.createServerSocket(port);
}
Here is my function for creating a SSLServerSocket. In the KeyManagerFactory initialization kmf.init(keyStore, keystorePassword.toCharArray()); I set the server keystore keystore with its password keystorePassword which are read with #Value Spring Annotation outside the function. This function will return the SSLServerSocket which will accept client connections in a new Thread like this:
while(true) {
SSLSocket clientSocket = (SSLSocket) serverSocket.accept();
}
Client code
SSLContext context = socketUtil.createSSLContext();
SSLSocketFactory sf = context.getSocketFactory();
SSLSocket socket = (SSLSocket) sf.createSocket(serverUrl, port);
This code creates the context I want to usem which is presented below, and creates the Socket to an address serverUrl and a port port:
public final SSLContext createSSLContext()
throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
InputStream in = getClass().getResourceAsStream("serverCert.pem");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null);
try {
X509Certificate cacert = (X509Certificate) cf.generateCertificate(in);
trustStore.setCertificateEntry("serverKey", cacert);
} finally {
IOUtils.closeQuietly(in);
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
Here, I load the Server certificate serverCert.pem which I generated from the keystore with the keytool utility. Then other parameters are setted, such as the algorithm or the protocol. Thus, the socket will trust connections from the issuer of the certificate.
The code is working just nice and smooth, but I'm concerned about security and good practices. So once again, is this a good approach to solve my problem?
Any clarifications about the process, code or misunderstandings are welcome. Thank you so much!
When using the apache httpcore5 java library to implement an HTTP server I can not find the proper idiom for rigging the socket to use TLS.
Under the older httpcore version 4.x I could
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(keystoreStream, keystorePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyPassword.toCharArray());
SSLContext sc = SSLContext.getInstance("TLSv1.2");
sc. init(kmf.getKeyManagers(), null, new SecureRandom());
ServerSocket sSock = sc.getServerSocketFactory().createServerSocket(port);
and then accept connections from that server socket and bind() those sockets to a DefaultHttpServerConnection and handleRequest() in a loop.
I have not yet found an example of how to write a TLS-capable server using the httpcore5 framework.
After some rummaging around I found H2ServerBootstrap.setTlsStrategy() and used it like this:
SSLContext ctx = buildAppSSLContext();
bootstrap.setTlsStrategy(new BasicServerTlsStrategy(ctx, new FixedPortStrategy(new int[]{port})));
I have a Java application that connects to another Java app through a socket with SSL, so my client JVM already has the -Djavax.net.ssl.keyStore and -Djavax.net.ssl.trustStore properties set.
This application needs to make some HTTP requests to a web server that requires client authentication. I can open the connection by using a URLConnection in Java which returns an HTTPSURLConnectionImpl.
The client certificate I want to present to the web server in the request is different than the one set as my JVM system property. Is there a way I can set a client cert. as a request property in the HTTPSURLConnectionImpl ?
Setting a SSL "client certificate" is not adequate directly through HTTPSURLConnectionImpl's request properties, because a digital signature is also required to prove you own the certificate. SSL already does all that automatically, so to makes sense to use that layer.
You have two ways to solve your issue going forward.
Through configuration
You can add you client key and certificate to your JVM KeyStore, it should be picked up at Runtime when the server asks for your client-side SSL authentication. (SSL/TLS is designed for that : the server will ask for a client certificate that is signed by it's trusted authority, which allows the SSL Engine to choose the right certificate, even when your KeyStore holds many).
Through Code
You can roll you own SSLContext using custom made KeyStore/TrustStores.
This is a bit complex (I won't elaborate on how to build Keystore instances in Java), but the gist of it is here :
public static void main(String[] args) throws Exception {
KeyStore clientKeyStore = ... // Whatever
KeyStore clientTrustStore = ... // Whatever you need to load
// We build the KeyManager (SSL client credentials we can send)
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(clientKeyStore, "password".toCharArray());
KeyManager[] km = keyFactory.getKeyManagers();
// We build the TrustManager (Server certificates we trust)
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(clientTrustStore);
TrustManager[] tm = trustFactory.getTrustManagers();
// We build a SSLContext with both our trust/key managers
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(km, tm, null);
SSLSocketFactory sslSf = sslContext.getSocketFactory();
// We prepare a URLConnection
URL url = new URL("https://www.google.com");
URLConnection urlConnection = url.openConnection();
// Before actually opening the sockets, we affect the SSLSocketFactory
HttpsURLConnection httpsUrlConnection = (HttpsURLConnection) urlConnection;
httpsUrlConnection.setSSLSocketFactory(sslSf);
// Ready to go !
}
I've been trying the code from this question within a Eclipse/Maven/Jetty project, and I am getting SSLHandshakeException from within the try{} block near sslSocket.startHandshake(). I've tried it with both my .p12 file loaded into the keyStore as well as my .pem. Neither of them work. Both worked in a PHP prototype I built. Also wasn't clear if the two uses of were of the same password.
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(getClass().getResourceAsStream("cert.p12"), "<password>".toCharArray());
KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance("SunX509");
keyMgrFactory.init(keyStore, "<password>".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(keyMgrFactory.getKeyManagers(), null, null);
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket(host, port);
String[] cipherSuites = sslSocket.getSupportedCipherSuites();
sslSocket.setEnabledCipherSuites(cipherSuites);
sslSocket.startHandshake();
}catch()...
I've checked the connection to Apples servers using
openssl s_client -connect gateway.sandbox.push.apple.com:2195
In return I get the following.
CONNECTED(00000003)
depth=1 /C=US/O=Entrust, Inc./OU=www.entrust.net/rpa is incorporated by reference/OU=(c) 2009 Entrust, Inc./CN=Entrust Certification Authority - L1C
verify error:num=20:unable to get local issuer certificate
verify return:0
18183:error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure:/SourceCache/OpenSSL098/OpenSSL098-44/src/ssl/s3_pkt.c:1102:SSL alert number 40
18183:error:140790E5:SSL routines:SSL23_WRITE:ssl handshake failure:/SourceCache/OpenSSL098/OpenSSL098-44/src/ssl/s23_lib.c:182:
I'm not really sure what's going on. Any ideas?
I would like to create an embedded ActiveMQ broker that listens on SSL protocol using client authentication mechanism (TLS).
Here's my code that expects to do so :
//loading keystore from file
KeyStore keystore = KeyStore.getInstance("pkcs12");
File ksfile = new File("/home/me/client1.pkcs12");
FileInputStream ksfis = new FileInputStream(ksfile);
keystore.load(ksfis, "password".toCharArray());
//loading truststore from file
KeyStore truststore = KeyStore.getInstance("jks");
truststore.load(new FileInputStream(new File("/home/me/client1.truststore")), "password"
.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory
.getDefaultAlgorithm());
kmf.init(keystore, "password".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(truststore);
//broker definition
String cfURI = "ssl://localhost:2032";
BrokerService brokerService = new BrokerService();
brokerService.addConnector(cfURI);
//configure ssl context for the broker
SslContext sslContext = new SslContext(kmf.getKeyManagers(),tmf.getTrustManagers(), null);
//need client authentication
sslContext.getSSLContext().getDefaultSSLParameters().setNeedClientAuth(true);
sslContext.getSSLContext().getDefaultSSLParameters().setWantClientAuth(true);
brokerService.setSslContext(sslContext);
brokerService.start();
When i execute the previous code in a main program, i get the following error :
GRAVE: Could not accept connection : javax.net.ssl.SSLException: No available certificate or key corresponds to the SSL cipher suites which are enabled.
Any suggestions are wellcome !
Thanks for reading.
Has your client set the certificate from the broker in its truststore? I'm afraid thats the problem you are running into.
Other than that, it would probably help if you paste the client code as well
I got this error by using ActiveMQConnectionFactory instead of ActiveMQSslConnectionFactory when connecting from the client