How to create a Server which accepts Client Certificate in Java
When you need to create a server which is requiring client certificate, you can use below code for that.
package testserver;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsExchange;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
public class TestServer {
/**
* @param args the command line arguments
*/
final static String Server_Password = "password";
final static String keystore = "D://NetbeansProjects/TestServer/src/testserver/keys/server.jks";
final static String truststore = "D://NetbeansProjects/TestServer/src/testserver/keys/servertrust.jks";
public static HttpsServer server;
public static void main(String[] args)throws Exception{
// TODO code application logic here;
HttpsServer server = makeNewServer();
server.start();
System.out.println("Server is running.\n"+server.getAddress().getHostName()); System.in.read();
server.stop(0);
}
public static HttpsServer makeNewServer() throws Exception {
server = HttpsServer.create(new InetSocketAddress(8000), 0);
SSLContext sslCon = createSSLContext();
ServerConfigurator conf = new ServerConfigurator(sslCon);
server.setHttpsConfigurator(conf);
server.createContext("/auth", new ServerHandler());
return server;
}
private static SSLContext createSSLContext() {
SSLContext sslContext = null;
KeyStore ks;
KeyStore ts;
try{
sslContext = SSLContext.getInstance("TLSv1.2");
ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keystore), Server_Password.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, Server_Password.toCharArray());
ts = KeyStore.getInstance("JKS");
ts.load(new FileInputStream(truststore), Server_Password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}
}
class ServerConfigurator extends HttpsConfigurator {
public ServerConfigurator(SSLContext sslContext) {
super(sslContext); }
@Override
public void configure(HttpsParameters params) {
SSLContext sslContext = getSSLContext();
SSLParameters sslParams = sslContext.getDefaultSSLParameters();
sslParams.setNeedClientAuth(true);
params.setNeedClientAuth(true);
params.setSSLParameters(sslParams);
}
}
class ServerHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
HttpsExchange ts = (HttpsExchange) t;
SSLSession sess = ts.getSSLSession();
System.out.printf("Responding to host: %s\n",sess.getPeerHost());
t.getResponseHeaders().set("Content-Type", "text/plain");
t.sendResponseHeaders(200,0);
String response = "Hello! I am a trusted server!\n";
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
Then you need to create Keystore and Truststore for your server.
What is the Keystore?
Keystore is used to store private key and own certificate which should present to other party for verifying own identity.
What is Truststore?
Truststore is used to save trusted CA certificates and public certificates of other parties. In java, cacerts is the Truststore and it is located at $JAVA_HOME/lib/security/cacerts.
Here, we are going to create self signed certificate which is signed by itself rather than trusted CA.
We can use java keytool to do this.
Create Server java key store
------------------------------------
keytool -genkey -alias server -keyalg RSA -keystore server.jks -validity 365 -dname "cn=$LOCALNAME, ou=Cert, o=Cert, c=CA" -storepass $PASSEORD -keypass $PASSWORD
Create client java key store
----------------------------------
keytool -genkey -alias MyClient -keyalg RSA -keystore MyClient.jks -validity 365 -dname "cn=$LOCALNAME, ou=Cert, o=Cert, c=CA" -storepass $PASS -keypass $PASS
Export Server certificate
-------------------------------
keytool -export -file server.cert -keystore server.jks -storepass $PASSWORD -alias server
Export Client certificate
--------------------------------
keytool -export -file MyClient.cert -keystore MyClient.jks -storepass $PASSWORD -alias MyClient
Import Server certificate to Client trust store
----------------------------------------------------------
keytool -import -file server.cert -alias server -keystore clienttrust.jks -storepass $PASSWORD
Import Client certificate to Server trust store
----------------------------------------------------------
keytool -import -file MyClient.cert -alias MyClient -keystore servertrust.jks -storepass $PASSWORD
package testserver;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsExchange;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
public class TestServer {
/**
* @param args the command line arguments
*/
final static String Server_Password = "password";
final static String keystore = "D://NetbeansProjects/TestServer/src/testserver/keys/server.jks";
final static String truststore = "D://NetbeansProjects/TestServer/src/testserver/keys/servertrust.jks";
public static HttpsServer server;
public static void main(String[] args)throws Exception{
// TODO code application logic here;
HttpsServer server = makeNewServer();
server.start();
System.out.println("Server is running.\n"+server.getAddress().getHostName()); System.in.read();
server.stop(0);
}
public static HttpsServer makeNewServer() throws Exception {
server = HttpsServer.create(new InetSocketAddress(8000), 0);
SSLContext sslCon = createSSLContext();
ServerConfigurator conf = new ServerConfigurator(sslCon);
server.setHttpsConfigurator(conf);
server.createContext("/auth", new ServerHandler());
return server;
}
private static SSLContext createSSLContext() {
SSLContext sslContext = null;
KeyStore ks;
KeyStore ts;
try{
sslContext = SSLContext.getInstance("TLSv1.2");
ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(keystore), Server_Password.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, Server_Password.toCharArray());
ts = KeyStore.getInstance("JKS");
ts.load(new FileInputStream(truststore), Server_Password.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
} catch (Exception e) {
e.printStackTrace();
}
return sslContext;
}
}
class ServerConfigurator extends HttpsConfigurator {
public ServerConfigurator(SSLContext sslContext) {
super(sslContext); }
@Override
public void configure(HttpsParameters params) {
SSLContext sslContext = getSSLContext();
SSLParameters sslParams = sslContext.getDefaultSSLParameters();
sslParams.setNeedClientAuth(true);
params.setNeedClientAuth(true);
params.setSSLParameters(sslParams);
}
}
class ServerHandler implements HttpHandler {
public void handle(HttpExchange t) throws IOException {
HttpsExchange ts = (HttpsExchange) t;
SSLSession sess = ts.getSSLSession();
System.out.printf("Responding to host: %s\n",sess.getPeerHost());
t.getResponseHeaders().set("Content-Type", "text/plain");
t.sendResponseHeaders(200,0);
String response = "Hello! I am a trusted server!\n";
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
Then you need to create Keystore and Truststore for your server.
What is the Keystore?
Keystore is used to store private key and own certificate which should present to other party for verifying own identity.
What is Truststore?
Truststore is used to save trusted CA certificates and public certificates of other parties. In java, cacerts is the Truststore and it is located at $JAVA_HOME/lib/security/cacerts.
Here, we are going to create self signed certificate which is signed by itself rather than trusted CA.
We can use java keytool to do this.
Create Server java key store
------------------------------------
keytool -genkey -alias server -keyalg RSA -keystore server.jks -validity 365 -dname "cn=$LOCALNAME, ou=Cert, o=Cert, c=CA" -storepass $PASSEORD -keypass $PASSWORD
Create client java key store
----------------------------------
keytool -genkey -alias MyClient -keyalg RSA -keystore MyClient.jks -validity 365 -dname "cn=$LOCALNAME, ou=Cert, o=Cert, c=CA" -storepass $PASS -keypass $PASS
Export Server certificate
-------------------------------
keytool -export -file server.cert -keystore server.jks -storepass $PASSWORD -alias server
Export Client certificate
--------------------------------
keytool -export -file MyClient.cert -keystore MyClient.jks -storepass $PASSWORD -alias MyClient
Import Server certificate to Client trust store
----------------------------------------------------------
keytool -import -file server.cert -alias server -keystore clienttrust.jks -storepass $PASSWORD
Import Client certificate to Server trust store
----------------------------------------------------------
keytool -import -file MyClient.cert -alias MyClient -keystore servertrust.jks -storepass $PASSWORD
Important Points
- When you are executing keytool commands in windows command prompt, command prompt should be opened as an administrator
- You should create your keystore at $JAVA_HOME/bin.
- It is good to use same password for keystore and keys to avoid unnecessary error
Exception Handling
- javax.net.ssl.SSLHandshakeException: java.security.cert.CertificateException: No name matching ssl.someUrl.de found -
Reason: When an HTTPS Client connects to a server, it verifies that the hostname in the certificate matches the hostname of the server. Hence, CN should be the hostname of the server that you are going to connect.
- Exception while sending data Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Resolution: Make sure you have imported the public certificate of the target instance into the Truststore
- Exception while sending data Caused by: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake Caused by: java.io.EOFException: SSL peer shut down incorrectly
Reason: Protocol version error. Check TLS version EX: sslContext = SSLContext.getInstance("TLSv1.2"); or in my case I have set sslParams.setNeedClientAuth(true); params.setNeedClientAuth(true); in my server code. But, I have tested this without importing client certificate to server truststore.
Thank you for the great post.
ReplyDeletePrancer is a pre-deployment and post-deployment multi-cloud validation framework for your Infrastructure as Code (IaC) pipeline and continuous compliance in the cloud.
https://www.prancer.io/