I wrote a client/server Java program using ServerSocket and Socket objects. I then modified the code to use SSLServerSocket and 'SSLSocket` however I am getting different Exceptions thrown including:
javax.net.ssl.SSLHandshakeException: no cipher suites in common
I am hoping to do as much programmatically as I can. I am also okay with self signed certificates.
One tutorial I followed suggested creating a certificate with the keytool java application, then moving that file into your java project. I have done that with the terminal command keytool -genkey -alias zastore -keyalg RSA -keystore za.store. I assigned the password to be password.
I then call the function System.setProperty in hopes of the SSLSockets working but it still does not.
Here is my server code
public class Server implements Runnable
{
private SSLServerSocket serverSocket;
private int portNumber;
private Thread acceptThread;
private LinkedList<Connection> connections;
private ConnectionListener connectionListener;
public Server(int port, ConnectionListener connectionListener)
{
this.connectionListener = connectionListener;
portNumber = port;
connections = new LinkedList<Connection>();
try
{
System.setProperty("javax.net.ssl.trustStore", "za.store");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLServerSocketFactory sslssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
serverSocket = (SSLServerSocket) sslssf.createServerSocket(portNumber,15);
}
catch (IOException e)
{
e.printStackTrace();
}
}
public void startListening()
{
acceptThread = new Thread(this);
acceptThread.start();
}
public void stopListening()
{
for(Connection c:connections)
{
c.stopListeningAndDisconnect();
}
try
{
serverSocket.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
#Override
public void run()
{
try
{
while(true)
{
SSLSocket s = (SSLSocket) serverSocket.accept();
Connection c = new Connection(s,connectionListener);
connections.add(c);
System.out.println("New Connection Established From"+s.getInetAddress().toString());
}
}
catch(java.net.SocketException e)
{
System.out.println("Listening thread terminated with exception.");
}
catch(IOException e)
{
e.printStackTrace();
}
}
public void removeConnection(Connection c)
{
connections.remove(c);
}
public void printConnections()
{
System.out.println("Number of connections "+connections.toString());
for(int i=0; i<connections.size(); i++)
{
System.out.println(connections.toString());
}
}
}
And then a snipbit of my client code that connects when a button is pressed:
#Override
public void actionPerformed(ActionEvent e)
{
if(e.getSource() == connect)
{
try
{
System.setProperty("javax.net.ssl.trustStore", "za.store");
System.setProperty("javax.net.ssl.keyStorePassword", "password");
SSLSocketFactory sslsf = (SSLSocketFactory)SSLSocketFactory.getDefault();
SSLSocket s = (SSLSocket)sslsf.createSocket(ipBox.getText(), Integer.parseInt(portBox.getText()));
Connection c = new Connection(s,parent);
parent.connectionSuccessful(c);
}
catch (NumberFormatException e1)
{
JOptionPane.showMessageDialog(this, "Error! Port number must be a number", "Error", JOptionPane.ERROR_MESSAGE);
}
catch (UnknownHostException e1)
{
JOptionPane.showMessageDialog(this, "Error! Unable to find that host", "Error", JOptionPane.ERROR_MESSAGE);
}
catch (IOException e1)
{
e1.printStackTrace();
}
}
}
One Stackoverflow article suggested that my server "doesn't have a certificate." I don't know what that means or how to go about getting one and locating it in the right place.
The following error may come due to various reasons:
javax.net.ssl.SSLHandshakeException: no cipher suites in common
The points to check while debugging:
The keystore and truststore are correctly loaded and used to create the socket connections
The certificate is compatible with the enabled cipher suites
At least one common cipher suite should be enabled in the client and the server and that cipher suite should be also compatible with the certificate
For e.g. in the following example, I am using Java 8 with the default set of cipher suites. The certificate I have generated is using ECDSA and SHA384, and hence when the TLS connection is established between the server and the client, I can see the negotiated cipher suite is TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 by enabling the debug (System.setProperty("javax.net.debug", "ssl");).
Following is a working example:
As a first step, a key pair and a certificate need to be created. For testing purpose, let's create a self-signed certificate and let's use the same certificate for both the server and the client:
keytool -genkeypair -alias server -keyalg EC \
-sigalg SHA384withECDSA -keysize 256 -keystore servercert.p12 \
-storetype pkcs12 -v -storepass abc123 -validity 10000 -ext san=ip:127.0.0.1
Let's now create the server:
package com.sapbasu.javastudy;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Objects;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.TrustManagerFactory;
/*
* keytool -genkeypair -alias server -keyalg EC \
* -sigalg SHA384withECDSA -keysize 256 -keystore servercert.p12 \
* -storetype pkcs12 -v -storepass abc123 -validity 10000 -ext san=ip:127.0.0.1
*/
public class TLSServer {
public void serve(int port, String tlsVersion, String trustStoreName,
char[] trustStorePassword, String keyStoreName, char[] keyStorePassword)
throws Exception {
Objects.requireNonNull(tlsVersion, "TLS version is mandatory");
if (port <= 0) {
throw new IllegalArgumentException(
"Port number cannot be less than or equal to 0");
}
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream tstore = TLSServer.class
.getResourceAsStream("/" + trustStoreName);
trustStore.load(tstore, trustStorePassword);
tstore.close();
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream kstore = TLSServer.class
.getResourceAsStream("/" + keyStoreName);
keyStore.load(kstore, keyStorePassword);
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyStorePassword);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
SecureRandom.getInstanceStrong());
SSLServerSocketFactory factory = ctx.getServerSocketFactory();
try (ServerSocket listener = factory.createServerSocket(port)) {
SSLServerSocket sslListener = (SSLServerSocket) listener;
sslListener.setNeedClientAuth(true);
sslListener.setEnabledProtocols(new String[] {tlsVersion});
// NIO to be implemented
while (true) {
try (Socket socket = sslListener.accept()) {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello World!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
Now create the client:
package com.sapbasu.javastudy;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Objects;
import javax.net.SocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManagerFactory;
public class TLSClient {
public String request(InetAddress serverHost, int serverPort,
String tlsVersion, String trustStoreName, char[] trustStorePassword,
String keyStoreName, char[] keyStorePassword) throws Exception {
Objects.requireNonNull(tlsVersion, "TLS version is mandatory");
Objects.requireNonNull(serverHost, "Server host cannot be null");
if (serverPort <= 0) {
throw new IllegalArgumentException(
"Server port cannot be lesss than or equal to 0");
}
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream tstore = TLSClient.class
.getResourceAsStream("/" + trustStoreName);
trustStore.load(tstore, trustStorePassword);
tstore.close();
TrustManagerFactory tmf = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream kstore = TLSClient.class
.getResourceAsStream("/" + keyStoreName);
keyStore.load(kstore, keyStorePassword);
KeyManagerFactory kmf = KeyManagerFactory
.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keyStorePassword);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(),
SecureRandom.getInstanceStrong());
SocketFactory factory = ctx.getSocketFactory();
try (Socket connection = factory.createSocket(serverHost, serverPort)) {
((SSLSocket) connection).setEnabledProtocols(new String[] {tlsVersion});
SSLParameters sslParams = new SSLParameters();
sslParams.setEndpointIdentificationAlgorithm("HTTPS");
((SSLSocket) connection).setSSLParameters(sslParams);
BufferedReader input = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
return input.readLine();
}
}
}
Finally, here's is a JUnit test to test the connection:
package com.sapbasu.javastudy;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.net.InetAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.jupiter.api.Test;
public class TLSServerClientTest {
private static final int SERVER_PORT = 8444;
private static final String TLS_VERSION = "TLSv1.2";
private static final int SERVER_COUNT = 1;
private static final String SERVER_HOST_NAME = "127.0.0.1";
private static final String TRUST_STORE_NAME = "servercert.p12";
private static final char[] TRUST_STORE_PWD = new char[] {'a', 'b', 'c', '1',
'2', '3'};
private static final String KEY_STORE_NAME = "servercert.p12";
private static final char[] KEY_STORE_PWD = new char[] {'a', 'b', 'c', '1',
'2', '3'};
#Test
public void whenClientSendsServerRequest_givenServerIsUp_returnsHelloWorld()
throws Exception {
TLSServer server = new TLSServer();
TLSClient client = new TLSClient();
System.setProperty("javax.net.debug", "ssl");
ExecutorService serverExecutor = Executors.newFixedThreadPool(SERVER_COUNT);
serverExecutor.submit(() -> {
try {
server.serve(SERVER_PORT, TLS_VERSION, TRUST_STORE_NAME,
TRUST_STORE_PWD, KEY_STORE_NAME, KEY_STORE_PWD);
} catch (Exception e) {
e.printStackTrace();
}
});
try {
String returnedValue = client.request(
InetAddress.getByName(SERVER_HOST_NAME), SERVER_PORT, TLS_VERSION,
TRUST_STORE_NAME, TRUST_STORE_PWD, KEY_STORE_NAME, KEY_STORE_PWD);
assertEquals("Hello World!", returnedValue);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}
Note: The certificate (servercert.p12 in this example) should be in the classpath. In this example, I've kept it in the test/resources folder of Maven folder structure so that the JUnit test can get it in the classpath.
Cipher Suite Background
When using TLS/SSL, the cryptographic algorithms to be used are determined by cipher suites. The server supports a set of cipher suites (you can enable or disable certain suites as per your needs and security level you want). The client also supports a set of cipher suites. During connection setup, the cipher suite to be used is negotiated between the client and the server. The client preference will be honored given that the server supports that particular cipher suite.
You'd find the list of cipher suites supported by the Sun Providers upto Java 8 here.
A typical cipher suite name looks like this:
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
Here,
ECDHE stands for Elliptic Curve Diffie Hellman Ephemeral. It's a Key exchange algorithm. The Elliptic variant (the first E) is used for performance, whereas the Ephemeral variant (the last E) is for forward secrecy. Forward secrecy means that if an attacker keeps recording all the communications over TLS and at a later point of time somehow gets hold of the private key, he/she cannot decrypt the past recorded communications.
ECDSA is a digital signature algorithm used for signing the key and is used for authenticating (verifying the integrity of) the shared secret. ECDSA is weaker and slower than the other authentication algorithms like HMAC. Yet it is used for shared key authentication because it does not need the verifier know the secret key used to create the authentication tag. The server can very well use its private key to verify the integrity of the message.
AES_128_GCM - Once a common secret key is shared between both the parties (usually a browser and a web server), a symmetric block cipher algorithm is used to encrypt the message exchanges between the parties. In this particular case, the block cipher AES with 128 bit key and GCM authentication mode is used.
SHA256 - Hashing algorithm for the PRF
You are setting the system properties wrong.
The server needs javax.net.keyStore (not trustStore) and javax.net.keyStorePassword -- and sometimes javax.net.keyStoreType but probably not in your case. For a server using self-signed cert, the client needs the truststore to contain that cert, and does not need any keystore at all. Although it's not documented, if these are on the same system you can use the server keystore (containing a privateKeyEntry) as the client truststore and JSSE automatically treats the privateKey's leaf cert as a trustedCert. In any other situation you need to extract the server's cert (not key), copy/send it to the client, and use keytool -importcert to put the cert (not key) into a keystore file which is used as truststore; this can be either a custom file (identified with the sysprops) or the default truststore at JRE/lib/security/cacerts (except on current Windows if JRE is under \Program Files[ (x86)] as is the installer default, because Windows now messes with people who try to change files there).
And these sysprops (when used) must be set BEFORE THE JSSE CLASSES ARE LOADED -- setting them when you call SSL[Server]SocketFactory is usually far too late. Usually they are set on the commandline that invokes Java with the -Dname=value option; that is guaranteed to be done early enough. If you can't do that, put the setProperty calls at the beginning of your main method, or the equivalent for a GUI.
This manifests as 'no common/shared cipher' because without a key+cert the server cannot support the ciphersuites that do server authentication, and Java/JSSE by default only enables ciphersuites that do server authentication because the others are insecure in many/most situations.
I'm sure I've answered the second part before, but can't find a dupe. The first part has occurred in many answers, but is usually not clearly presented in the question.
Related
i have a problem with the BouncyCastle-Format on Android 4.1.2.
In my app i use SSL-Pinning to protect my customers from MITM-Attack. The public ssl certificate is stored in the raw resource directory. All works fine, but on Android 4.1.2 it will crash by initialize the KeyStore.
Currently affected devices are: Samsung GT18190/GT18190N ; Sony ST26I ; LG P880 - all with Android 4.1.2
Here is my Code for creating a SSLSocketFactory:
import android.content.Context;
import java.io.InputStream;
import java.security.KeyStore;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
public class SSLSocket {
public static javax.net.ssl.SSLSocketFactory newSslSocketFactory(Context ctx) {
char[] KEYSTORE_PASSWORD = "MYKEYSTOREPASSWORD".toCharArray();
try {
KeyStore trusted = KeyStore.getInstance("BKS");
InputStream in = ctx.getResources().openRawResource(R.raw.ssl);
try {
trusted.load(in, KEYSTORE_PASSWORD);
} finally {
in.close();
}
String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
tmf.init(trusted);
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, tmf.getTrustManagers(), null);
return context.getSocketFactory();
} catch (Exception e) {
throw new AssertionError(e);
}
}
}
The following exception is thrown:
java.lang.AssertionError: java.security.KeyStoreException: java.security.NoSuchAlgorithmException: KeyStore BKS implementation not found
Have anyone an idea for solving the issue for Android 4.1.2?
First off, I know there is a similar question here but it doesn't answer my doubts.
I have a FTPS server (vsftpd) configured with SSL.
I've generated the appropriate keys with:
openssl req -x509 -nodes -days 1825 -newkey rsa:2048 \
-keyout private/vsftpd.key \
-out certs/vsftpd.crt
And configured the respective conf file in the server.
Now, in the Java client code with Apache commons net, I'm able to communicate with the server over SSL with something like this:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;
public class CommonsNetFTPSTest {
public static void main(String[] args) throws Exception {
System.setProperty("javax.net.debug", "ssl");
String server = "XXX.XXX.XXX.XXX";
String username = "USER_TEST";
String password = "ABCD1234";
String remoteFile = "/Data/Input/PH240819";
String localFile = "PH240819";
String protocol = "SSL"; // TLS / null (SSL)
int port = 990;
int timeoutInMillis = 10000;
boolean isImpicit = true;
FTPSClient client = new FTPSClient(protocol, isImpicit);
client.setDataTimeout(timeoutInMillis);
client.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
System.out.println("################ Connecting to Server ################################");
try
{
int reply;
System.out.println("################ Connect Call ################################");
client.connect(server, port);
client.login(username, password);
System.out.println("################ Login Success ################################");
//client.setFileType(FTP.BINARY_FILE_TYPE);
client.setFileType(FTP.NON_PRINT_TEXT_FORMAT);
client.execPBSZ(0); // Set protection buffer size
client.execPROT("P"); // Set data channel protection to private
client.enterLocalPassiveMode();
System.out.println("Connected to " + server + ".");
reply = client.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
client.disconnect();
System.err.println("FTP server refused connection.");
System.exit(1);
}
client.listFiles();
boolean retrieved = client.retrieveFile(remoteFile, new FileOutputStream(localFile));
}
catch (Exception e)
{
if (client.isConnected())
{
try
{
client.disconnect();
}
catch (IOException ex)
{
ex.printStackTrace();
}
}
System.err.println("Could not connect to server.");
e.printStackTrace();
return;
}
finally
{
//client.disconnect();
client.logout();
System.out.println("# client disconnected");
}
}
}
But in that way my client is accepting any certificate, and I guess it's possible a man-in-the-middle attack. So I'd like to tell my client what certificates to accept, and only connect when the certificate is valid.
Now, I have both files generated previously: vsftpd.key and vsftpd.crt, and I've imported the crt file in a local keystore like this:
keytool -import -alias alias -file vsftps.crt -keypass keypass -keystore mykeystore.jks -storepass Hello1
My question is, How do I tell my client that use the certificate from mykeystore? what else am I missing?. Thanks
Ok, now I have it.
I was doing it wrong from the beginning. To start with, you need to convert the two files (vsftpd.crt and vsftpd.key) into a single PKCS12 file.
openssl pkcs12 -export -in vsftpd.crt -inkey vsftpd.key > vsftpd.p12
Next, you need to import the PKCS12 file into a keystore:
keytool -importkeystore -srckeystore vsftpd.p12 -destkeystore keystore.jks -srcstoretype pkcs12
Detailed instructions [here].2
Finally, you just need to instantiate a trust manager with the generated keystore, and hand it to the FTPSClient. Something like:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import javax.net.ssl.X509TrustManager;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPSClient;
import org.apache.commons.net.io.Util;
import org.apache.commons.net.util.TrustManagerUtils;
public method() throws IOException, GeneralSecurityException{
File storeFile = new File("path/to/keystore");
KeyStore keyStore = loadStore("JKS", storeFile, "password");
X509TrustManager defaultTrustManager = TrustManagerUtils.getDefaultTrustManager(keyStore);
client = new FTPSClient(properties.getProtocol(), isImpicit);
client.setTrustManager(defaultTrustManager);
logOutput = new LogOutputStream(log, Level.INFO);
}
//Helper method from apache: http://commons.apache.org/proper/commons-net/apidocs/index.html?org/apache/commons/net/util/KeyManagerUtils.html
private KeyStore loadStore(String storeType, File storePath, String storePass)
throws KeyStoreException, IOException, GeneralSecurityException {
KeyStore ks = KeyStore.getInstance(storeType);
FileInputStream stream = null;
try {
stream = new FileInputStream(storePath);
ks.load(stream, storePass.toCharArray());
} finally {
Util.closeQuietly(stream);
}
return ks;
}
you have to generate your own keystore from previous comment.
Now use this link https://issues.apache.org/jira/browse/NET-326
Find this comment (Bogdan Drozdowski added a comment - 10/Mar/11 15:16) and do FTPSCLient(SSLContext sslContext) constructor like in this comment, and your ftpsClient will work with certificate and private key auth.
This code connects to a HTTPS site and I am assuming I am not verifying the certificate. But why don't I have to install a certificate locally for the site? Shouldn't I have to install a certificate locally and load it for this program or is it downloaded behind the covers? Is the traffic between the client to the remote site still encrypted in transmission?
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class TestSSL {
public static void main(String[] args) throws Exception {
// Create a trust manager that does not validate certificate chains
TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
} };
// Install the all-trusting trust manager
final SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
URL url = new URL("https://www.google.com");
URLConnection con = url.openConnection();
final Reader reader = new InputStreamReader(con.getInputStream());
final BufferedReader br = new BufferedReader(reader);
String line = "";
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
} // End of main
} // End of the class //
The reason why you don't have to load a certificate locally is that you've explicitly chosen not to verify the certificate, with this trust manager that trusts all certificates.
The traffic will still be encrypted, but you're opening the connection to Man-In-The-Middle attacks: you're communicating secretly with someone, you're just not sure whether it's the server you expect, or a possible attacker.
If your server certificate comes from a well-known CA, part of the default bundle of CA certificates bundled with the JRE (usually cacerts file, see JSSE Reference guide), you can just use the default trust manager, you don't have to set anything here.
If you have a specific certificate (self-signed or from your own CA), you can use the default trust manager or perhaps one initialised with a specific truststore, but you'll have to import the certificate explicitly in your trust store (after independent verification), as described in this answer. You may also be interested in this answer.
But why don't I have to install a certificate locally for the site?
Well the code that you are using is explicitly designed to accept the certificate without doing any checks whatsoever. This is not good practice ... but if that is what you want to do, then (obviously) there is no need to install a certificate that your code is explicitly ignoring.
Shouldn't I have to install a certificate locally and load it for this program or is it downloaded behind the covers?
No, and no. See above.
Is the traffic between the client to the remote site still encrypted in transmission?
Yes it is. However, the problem is that since you have told it to trust the server's certificate without doing any checks, you don't know if you are talking to the real server, or to some other site that is pretending to be the real server. Whether this is a problem depends on the circumstances.
If we used the browser as an example, typically a browser doesn't ask the user to explicitly install a certificate for each ssl site visited.
The browser has a set of trusted root certificates pre-installed. Most times, when you visit an "https" site, the browser can verify that the site's certificate is (ultimately, via the certificate chain) secured by one of those trusted certs. If the browser doesn't recognize the cert at the start of the chain as being a trusted cert (or if the certificates are out of date or otherwise invalid / inappropriate), then it will display a warning.
Java works the same way. The JVM's keystore has a set of trusted certificates, and the same process is used to check the certificate is secured by a trusted certificate.
Does the java https client api support some type of mechanism to download certificate information automatically?
No. Allowing applications to download certificates from random places, and install them (as trusted) in the system keystore would be a security hole.
Use the latest X509ExtendedTrustManager instead of X509Certificate as advised here: java.security.cert.CertificateException: Certificates does not conform to algorithm constraints
package javaapplication8;
import java.io.InputStream;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
/**
*
* #author hoshantm
*/
public class JavaApplication8 {
/**
* #param args the command line arguments
* #throws java.lang.Exception
*/
public static void main(String[] args) throws Exception {
/*
* fix for
* Exception in thread "main" 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
*/
TrustManager[] trustAllCerts = new TrustManager[]{
new X509ExtendedTrustManager() {
#Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
#Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
#Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
#Override
public void checkClientTrusted(X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] xcs, String string, Socket socket) throws CertificateException {
}
#Override
public void checkClientTrusted(X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
}
#Override
public void checkServerTrusted(X509Certificate[] xcs, String string, SSLEngine ssle) throws CertificateException {
}
}
};
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// Create all-trusting host name verifier
HostnameVerifier allHostsValid = new HostnameVerifier() {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
};
// Install the all-trusting host verifier
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
/*
* end of the fix
*/
URL url = new URL("https://10.52.182.224/cgi-bin/dynamic/config/panel.bmp");
URLConnection con = url.openConnection();
//Reader reader = new ImageStreamReader(con.getInputStream());
InputStream is = new URL(url.toString()).openStream();
// Whatever you may want to do next
}
}
Java and HTTPS url connection without downloading certificate
If you really want to avoid downloading the server's certificate, then use an anonymous protocol like Anonymous Diffie-Hellman (ADH). The server's certificate is not sent with ADH and friends.
You select an anonymous protocol with setEnabledCipherSuites. You can see the list of cipher suites available with getEnabledCipherSuites.
Related: that's why you have to call SSL_get_peer_certificate in OpenSSL. You'll get a X509_V_OK with an anonymous protocol, and that's how you check to see if a certificate was used in the exchange.
But as Bruno and Stephed C stated, its a bad idea to avoid the checks or use an anonymous protocol.
Another option is to use TLS-PSK or TLS-SRP. They don't require server certificates either. (But I don't think you can use them).
The rub is, you need to be pre-provisioned in the system because TLS-PSK is Pres-shared Secret and TLS-SRP is Secure Remote Password. The authentication is mutual rather than server only.
In this case, the mutual authentication is provided by a property that both parties know the shared secret and arrive at the same premaster secret; or one (or both) does not and channel setup fails. Each party proves knowledge of the secret is the "mutual" part.
Finally, TLS-PSK or TLS-SRP don't do dumb things, like cough up the user's password like in a web app using HTTP (or over HTTPS). That's why I said each party proves knowledge of the secret...
A simple, but not pure java solution, is to shell out to curl from java, which gives you complete control over how the request is done. If you're just doing this for something simple, this allows you to ignore certificate errors at times, by using this method. This example shows how to make a request against a secure server with a valid or invalid certificate, pass in a cookie, and get the output using curl from java.
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
public class MyTestClass
{
public static void main(String[] args)
{
String url = "https://www.google.com";
String sessionId = "faf419e0-45a5-47b3-96d1-8c62b2a3b558";
// Curl options are:
// -k: ignore certificate errors
// -L: follow redirects
// -s: non verbose
// -H: add a http header
String[] command = { "curl", "-k", "-L", "-s", "-H", "Cookie: MYSESSIONCOOKIENAME=" + sessionId + ";", "-H", "Accept:*/*", url };
String output = executeShellCmd(command, "/tmp", true, true);
System.out.println(output);
}
public String executeShellCmd(String[] command, String workingFolder, boolean wantsOutput, boolean wantsErrors)
{
try
{
ProcessBuilder pb = new ProcessBuilder(command);
File wf = new File(workingFolder);
pb.directory(wf);
Process proc = pb.start();
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
StringBuffer sb = new StringBuffer();
String newLine = System.getProperty("line.separator");
String s;
// read stdout from the command
if (wantsOutput)
{
while ((s = stdInput.readLine()) != null)
{
sb.append(s);
sb.append(newLine);
}
}
// read any errors from the attempted command
if (wantsErrors)
{
while ((s = stdError.readLine()) != null)
{
sb.append(s);
sb.append(newLine);
}
}
String result = sb.toString();
return result;
}
catch (IOException e)
{
throw new RuntimeException("Problem occurred:", e);
}
}
}
If you are using any Payment Gateway to hit any url just to send a message, then i used a webview by following it :
How can load https url without use of ssl in android webview
and make a webview in your activity with visibility gone. What you need to do : just load that webview.. like this:
webViewForSms.setWebViewClient(new SSLTolerentWebViewClient());
webViewForSms.loadUrl(" https://bulksms.com/" +
"?username=test&password=test#123&messageType=text&mobile="+
mobileEditText.getText().toString()+"&senderId=ATZEHC&message=Your%20OTP%20for%20A2Z%20registration%20is%20124");
Easy.
You will get this: SSLTolerentWebViewClient from this link:
How can load https url without use of ssl in android webview
I'm working on a project that I want to add SSL to, so I created a simple client/server test implementation to see if it worked and I get a NoSuchAlgorithmException. The following is my server code which is throwing the exception:
import java.io.*;
import java.net.*;
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 java.security.cert.CertificateException;
import javax.net.ssl.*;
public class SslServer
{
private static final int PORT = 5555;
public static void main(String[] args)
{
SecureRandom sr = new SecureRandom();
sr.nextInt();
try {
//client.public is the keystore file that holds the client's public key (created with keytool)
KeyStore clientKeyStore = KeyStore.getInstance("JKS");
clientKeyStore.load(new FileInputStream("client.public"), "clientpublicpw".toCharArray());
//server.private is the key pair for the server (created with keytool)
KeyStore serverKeyStore = KeyStore.getInstance("JKS");
clientKeyStore.load(new FileInputStream("server.private"), "serverprivatepw".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(clientKeyStore);
//This next line is where the exception occurs
KeyManagerFactory kmf = KeyManagerFactory.getInstance("TLS");
kmf.init(serverKeyStore, "serverprivatepw".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), sr);
SSLServerSocketFactory sf = sslContext.getServerSocketFactory();
SSLServerSocket ss = (SSLServerSocket)sf.createServerSocket(SslServer.PORT);
ss.setNeedClientAuth(true);
BufferedReader in = new BufferedReader(new InputStreamReader(ss.accept().getInputStream()));
String line = null;
while((line = in.readLine()) != null)
{
System.out.println(line);
}
in.close();
ss.close();
} catch (IOException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (CertificateException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
} catch (UnrecoverableKeyException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
}
}
}
The stacktrace I get is:
java.security.NoSuchAlgorithmException: TLS KeyManagerFactory not available
at sun.security.jca.GetInstance.getInstance(Unknown Source)
at javax.net.ssl.KeyManagerFactory.getInstance(Unknown Source)
at SslServer.main(SslServer.java:32)
I tried replacing "TLS" with "SSL" and I still got the same exception. That Didn't make sense to me. How can TLS and SSL not be supported? This is my first time trying to implement SSL and it seems difficult to find good resources about this with code examples that are well explained. Can anyone tell me why I am getting this exception or point out something wrong with my code?
There are a number of problems:
It's called TLS (Transport Layer Security), not TSL (for the SSLContext).
I'd suggest using the default here: TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) (The default will be PKIX on the Oracle JRE`)
(EDIT:) The default KeyManagerFactory is SunX509 (TLS doesn't exist here). Again, use getDefaultAlgorithm().
You should close your FileInputStream once you've read them.
It's not clear why you have both a client and a server keystore at the same place. These should be two programs: one for the client and the server (and setNeedClientAuth(true) is only useful on the server side). It would be clearer to call it something else than "client store" if it's effectively your keystore. (In addition, since you seem to be learning how to make this work, I'd suggest trying without client-certificate authentication first, in which case, the server won't need a truststore: use null as a second parameter of SSLContext.init(...) to use the default value.)
DO NOT give the server keystore to the client. Only export its certificate into a new keystore which you will use as a trust store. Each entity (client and server) should keep their own private keys private.
It's not so much the public key (only) of the remote party you want in your trust-store: it's going to be its certificate. Make sure you haven't only imported its public key, but the entire certificate.
For clarify, keep the appropriate extensions to your files: use .jks for your JKS keystore, this will save you headaches later.
You can use null for the SecureRandom in SSLContext.init(...): this will use the default value according to the security provider.
Something like this should work better:
KeyStore trustStore = KeyStore.getInstance("JKS");
InputStream tsis = new FileInputStream("trustedcerts.jks");
trustStore.load(tsis, "clientpublicpw".toCharArray());
tsis.close();
KeyStore serverKeyStore = KeyStore.getInstance("JKS");
InputStream ksis = new FileInputStream("server.jks");
clientKeyStore.load(ksis.close(), "serverprivatepw".toCharArray());
ksis.close();
TrustManagerFactory tmf =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(serverKeyStore, "serverprivatepw".toCharArray());
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory sf = sslContext.getServerSocketFactory();
SSLServerSocket ss = (SSLServerSocket)sf.createServerSocket(SslServer.PORT);
ss.setNeedClientAuth(true);
See http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#SupportClasses for examples, and for the names of the supported algorithm. It seems that "SunX509" and "NewSunX509" are the algorithms supported by KeyManagerFactory. And the protocol is named TLS, not TSL.
The correct SSLContext name is "TLS". A list of standard algorithm names can be found here.
I need to set up a really lightweight HTTPS server for a Java application. It's a simulator that's being used in our development labs to simulate the HTTPS connections accepted by a piece of equipment in the wild. Because it's purely a lightweight development tool and isn't used in production in any way at all, I'm quite happy to bypass certifications and as much negotiation as I can.
I'm planning on using the HttpsServer class in Java 6 SE but I'm struggling to get it working. As a test client, I'm using wget from the cygwin command line (wget https://[address]:[port]) but wget reports that it was "Unable to establish SSL connection".
If I run wget with the -d option for debugging it tells me "SSL handshake failed".
I've spent 30 minutes googling this and everything seems to just point back to the fairly useless Java 6 documentation that describes the methods but doesn't actually talk about how to get the darn thing talking or provide any example code at all.
Can anyone nudge me in the right direction?
What I eventually used was this:
try {
// Set up the socket address
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(), config.getHttpsPort());
// Initialise the HTTPS server
HttpsServer httpsServer = HttpsServer.create(address, 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
// Initialise the keystore
char[] password = "simulator".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream("lig.keystore");
ks.load(fis, password);
// Set up the key manager factory
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, password);
// Set up the trust manager factory
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
// Set up the HTTPS context and parameters
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
public void configure(HttpsParameters params) {
try {
// Initialise the SSL context
SSLContext c = SSLContext.getDefault();
SSLEngine engine = c.createSSLEngine();
params.setNeedClientAuth(false);
params.setCipherSuites(engine.getEnabledCipherSuites());
params.setProtocols(engine.getEnabledProtocols());
// Get the default parameters
SSLParameters defaultSSLParameters = c.getDefaultSSLParameters();
params.setSSLParameters(defaultSSLParameters);
} catch (Exception ex) {
ILogger log = new LoggerFactory().getLogger();
log.exception(ex);
log.error("Failed to create HTTPS port");
}
}
});
LigServer server = new LigServer(httpsServer);
joinableThreadList.add(server.getJoinableThread());
} catch (Exception exception) {
log.exception(exception);
log.error("Failed to create HTTPS server on port " + config.getHttpsPort() + " of localhost");
}
To generate a keystore:
$ keytool -genkeypair -keyalg RSA -alias self_signed -keypass simulator \
-keystore lig.keystore -storepass simulator
See also here.
Potentially storepass and keypass might be different, in which case the ks.load and kmf.init must use storepass and keypass, respectively.
I updated your answer for a HTTPS server (not socket-based). It might help with CSRF and AJAX calls.
import java.io.*;
import java.net.InetSocketAddress;
import java.lang.*;
import java.net.URL;
import com.sun.net.httpserver.HttpsServer;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import com.sun.net.httpserver.*;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URLConnection;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.cert.X509Certificate;
import java.net.InetAddress;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsExchange;
public class SimpleHTTPSServer {
public static class MyHandler implements HttpHandler {
#Override
public void handle(HttpExchange t) throws IOException {
String response = "This is the response";
HttpsExchange httpsExchange = (HttpsExchange) t;
t.getResponseHeaders().add("Access-Control-Allow-Origin", "*");
t.sendResponseHeaders(200, response.getBytes().length);
OutputStream os = t.getResponseBody();
os.write(response.getBytes());
os.close();
}
}
/**
* #param args
*/
public static void main(String[] args) throws Exception {
try {
// setup the socket address
InetSocketAddress address = new InetSocketAddress(8000);
// initialise the HTTPS server
HttpsServer httpsServer = HttpsServer.create(address, 0);
SSLContext sslContext = SSLContext.getInstance("TLS");
// initialise the keystore
char[] password = "password".toCharArray();
KeyStore ks = KeyStore.getInstance("JKS");
FileInputStream fis = new FileInputStream("testkey.jks");
ks.load(fis, password);
// setup the key manager factory
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, password);
// setup the trust manager factory
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ks);
// setup the HTTPS context and parameters
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext) {
public void configure(HttpsParameters params) {
try {
// initialise the SSL context
SSLContext context = getSSLContext();
SSLEngine engine = context.createSSLEngine();
params.setNeedClientAuth(false);
params.setCipherSuites(engine.getEnabledCipherSuites());
params.setProtocols(engine.getEnabledProtocols());
// Set the SSL parameters
SSLParameters sslParameters = context.getSupportedSSLParameters();
params.setSSLParameters(sslParameters);
} catch (Exception ex) {
System.out.println("Failed to create HTTPS port");
}
}
});
httpsServer.createContext("/test", new MyHandler());
httpsServer.setExecutor(null); // creates a default executor
httpsServer.start();
} catch (Exception exception) {
System.out.println("Failed to create HTTPS server on port " + 8000 + " of localhost");
exception.printStackTrace();
}
}
}
To create a self-signed certificate:
keytool -genkeypair -keyalg RSA -alias selfsigned -keystore testkey.jks -storepass password -validity 360 -keysize 2048
With ServerSocket
You can use the class that HttpsServer is built around to be even more light-weight: ServerSocket.
Single-threaded
The following program is a very simple, single-threaded server listening on port 8443. Messages are encrypted with TLS using the keys in ./keystore.jks:
public static void main(String... args) {
var address = new InetSocketAddress("0.0.0.0", 8443);
startSingleThreaded(address);
}
public static void startSingleThreaded(InetSocketAddress address) {
System.out.println("Start single-threaded server at " + address);
try (var serverSocket = getServerSocket(address)) {
var encoding = StandardCharsets.UTF_8;
// This infinite loop is not CPU-intensive since method "accept" blocks
// until a client has made a connection to the socket
while (true) {
try (var socket = serverSocket.accept();
// Use the socket to read the client's request
var reader = new BufferedReader(new InputStreamReader(
socket.getInputStream(), encoding.name()));
// Writing to the output stream and then closing it sends
// data to the client
var writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), encoding.name()))
) {
getHeaderLines(reader).forEach(System.out::println);
writer.write(getResponse(encoding));
writer.flush();
} catch (IOException e) {
System.err.println("Exception while handling connection");
e.printStackTrace();
}
}
} catch (Exception e) {
System.err.println("Could not create socket at " + address);
e.printStackTrace();
}
}
private static ServerSocket getServerSocket(InetSocketAddress address)
throws Exception {
// Backlog is the maximum number of pending connections on the socket,
// 0 means that an implementation-specific default is used
int backlog = 0;
var keyStorePath = Path.of("./keystore.jks");
char[] keyStorePassword = "pass_for_self_signed_cert".toCharArray();
// Bind the socket to the given port and address
var serverSocket = getSslContext(keyStorePath, keyStorePassword)
.getServerSocketFactory()
.createServerSocket(address.getPort(), backlog, address.getAddress());
// We don't need the password anymore → Overwrite it
Arrays.fill(keyStorePassword, '0');
return serverSocket;
}
private static SSLContext getSslContext(Path keyStorePath, char[] keyStorePass)
throws Exception {
var keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(keyStorePath.toFile()), keyStorePass);
var keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
keyManagerFactory.init(keyStore, keyStorePass);
var sslContext = SSLContext.getInstance("TLS");
// Null means using default implementations for TrustManager and SecureRandom
sslContext.init(keyManagerFactory.getKeyManagers(), null, null);
return sslContext;
}
private static String getResponse(Charset encoding) {
var body = "The server says hi 👋\r\n";
var contentLength = body.getBytes(encoding).length;
return "HTTP/1.1 200 OK\r\n" +
String.format("Content-Length: %d\r\n", contentLength) +
String.format("Content-Type: text/plain; charset=%s\r\n",
encoding.displayName()) +
// An empty line marks the end of the response's header
"\r\n" +
body;
}
private static List<String> getHeaderLines(BufferedReader reader)
throws IOException {
var lines = new ArrayList<String>();
var line = reader.readLine();
// An empty line marks the end of the request's header
while (!line.isEmpty()) {
lines.add(line);
line = reader.readLine();
}
return lines;
}
Here's a project using this socket-based approach.
Multi-threaded
To use more than one thread for the server, you can employ a thread pool:
public static void startMultiThreaded(InetSocketAddress address) {
try (var serverSocket = getServerSocket(address)) {
System.out.println("Started multi-threaded server at " + address);
// A cached thread pool with a limited number of threads
var threadPool = newCachedThreadPool(8);
var encoding = StandardCharsets.UTF_8;
// This infinite loop is not CPU-intensive since method "accept" blocks
// until a client has made a connection to the socket
while (true) {
try {
var socket = serverSocket.accept();
// Create a response to the request on a separate thread to
// handle multiple requests simultaneously
threadPool.submit(() -> {
try ( // Use the socket to read the client's request
var reader = new BufferedReader(new InputStreamReader(
socket.getInputStream(), encoding.name()));
// Writing to the output stream and then closing it
// sends data to the client
var writer = new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream(), encoding.name()))
) {
getHeaderLines(reader).forEach(System.out::println);
writer.write(getResponse(encoding));
writer.flush();
// We're done with the connection → Close the socket
socket.close();
} catch (Exception e) {
System.err.println("Exception while creating response");
e.printStackTrace();
}
});
} catch (IOException e) {
System.err.println("Exception while handling connection");
e.printStackTrace();
}
}
} catch (Exception e) {
System.err.println("Could not create socket at " + address);
e.printStackTrace();
}
}
private static ExecutorService newCachedThreadPool(int maximumNumberOfThreads) {
return new ThreadPoolExecutor(0, maximumNumberOfThreads,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>());
}
Create a certificate
Use the keytool to create a self-signed certificate (you can get a proper certificate from Let's Encrypt for free):
keytool -genkeypair -keyalg RSA -alias selfsigned -keystore keystore.jks \
-storepass pass_for_self_signed_cert \
-dname "CN=localhost, OU=Developers, O=Bull Bytes, L=Linz, C=AT"
Contact the server
After starting the server, connect to it with curl:
curl -k https://localhost:8443
This will fetch a message from the server:
The server says hi 👋
Inspect which protocol and cipher suite were established by curl and your server with
curl -kv https://localhost:8443
Using JDK 13 and curl 7.66.0, this produced
SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
Refer to Java Network Programming by Elliotte Rusty Harold for more on the topic.
Although this question is really old, someone mentioned me this topic and asked if it could be simplified. Most of the answers demonstrate very well how to setup a simple https server with sun, but I want to provide an alternative which is hopefully a bit easier.
For this setup I am assuming you already have the keystore and truststore in place.
The rest endpoint:
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class HelloWorldController implements HttpHandler {
#Override
public void handle(HttpExchange exchange) throws IOException {
try (OutputStream responseBody = exchange.getResponseBody()) {
exchange.getResponseHeaders().set("Content-Type", "text/plain");
String payload = "Hello";
exchange.sendResponseHeaders(200, payload.length());
responseBody.write(payload.getBytes(StandardCharsets.UTF_8));
}
}
}
Server configuration:
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
import nl.altindag.server.controller.HelloWorldController;
import nl.altindag.ssl.SSLFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
public class App {
public static void main(String[] args) throws IOException {
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial("keystore.jks", "secret".toCharArray())
.withTrustMaterial("truststore.jks", "secret".toCharArray())
.build();
InetSocketAddress socketAddress = new InetSocketAddress(8443);
HttpsServer httpsServer = HttpsServer.create(socketAddress, 0);
httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslFactory.getSslContext()) {
#Override
public void configure(HttpsParameters params) {
params.setSSLParameters(sslFactory.getSslParameters());
}
});
httpsServer.createContext("/api/hello", new HelloWorldController());
httpsServer.setExecutor(Executors.newCachedThreadPool());
httpsServer.start();
}
}
I need to add some disclaimer here... I use SSLFactory class from the Github - SSLContext-Kickstart library to easily construct a SSLContext. It is maintained by me. You don't need to use it as others have provided a way to construct it with just plain java.
Just a reminder to others: com.sun.net.httpserver.HttpsServer in the solutions above is not part of the Java standard. Although it is bundled with the Oracle/OpenJDK JVM, it is not included in all JVMs so this will not work out of the box everywhere.
There are several lightweight HTTP servers out there that you can embed in your application that support HTTPS and run on any JVM.
One of them is JLHTTP - The Java Lightweight HTTP Server which is a tiny one-file server (or ~50K/35K jar) with no dependencies. Setting up the keystore, SSLContext etc. is similar to the above, since it also relies on the standard JSSE implementation, or you can specify the standard system properties to configure SSL. You can see the FAQ or the code and its documentation for details.
Disclaimer: I'm the author of JLHTTP. You can check it out for yourself and determine if it suits your needs. I hope you find it useful :-)