5

I am trying to setup a secure WebSocket server with Jetty like the following:

import java.util.ArrayList;
import java.util.List;

import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;


public class WebSocketServer
{
    private Server server;
    private String host="localhost";
    private int port=8080;
    private String keyStorePath = "C:\\keystore";
    private String keyStorePassword="password";
    private String keyManagerPassword="password";
    private List<Handler> webSocketHandlerList = new ArrayList();
    MessageHandler messagehandler;

    public WebSocketServer()
    {
        System.out.println("WebSocketServer");

        server = new Server();

        // connector configuration
        SslContextFactory sslContextFactory = new SslContextFactory();
        sslContextFactory.setKeyStorePath(keyStorePath);
        sslContextFactory.setKeyStorePassword(keyStorePassword);
        sslContextFactory.setKeyManagerPassword(keyManagerPassword);
        SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
        HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(new HttpConfiguration());
        ServerConnector sslConnector = new ServerConnector(server, sslConnectionFactory, httpConnectionFactory);
        sslConnector.setHost(host);
        sslConnector.setPort(port);
        server.addConnector(sslConnector);

        // handler configuration
        HandlerCollection handlerCollection = new HandlerCollection();
        handlerCollection.setHandlers(webSocketHandlerList.toArray(new Handler[0]));
        server.setHandler(handlerCollection);

        WebSocketHandler wsHandler = new WebSocketHandler() {
            @Override
            public void configure(WebSocketServletFactory webSocketServletFactory) {
                webSocketServletFactory.register(MyWebSocketHandler.class);
            }
        };
        ContextHandler wsContextHandler = new ContextHandler();
        wsContextHandler.setHandler(wsHandler);
        wsContextHandler.setContextPath("/");  // this context path doesn't work ftm
        webSocketHandlerList.add(wsHandler);

        messagehandler = new MessageHandler();
        new Thread(messagehandler).start();

        try {
            server.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

The keystore file is created with the following command found here in the jdk/bin folder:

keytool.exe -keystore keystore -alias jetty -genkey -keyalg RSA

After that I moved the file into the C directory for easy path usage.

With this configuration my server seems to start without any problems. So I am trying to connect to it with my website like this:

ws = new WebSocket("wss://localhost:8080/");

This doesn't work at all. Like written here, I think I have to configure the SSL certificate. Furthermore, to create the server I used this tutorial and for the java client side they implement a truststore. Do I have to do something similar for JavaScript?

Community
  • 1
  • 1
Steckdoserich
  • 834
  • 2
  • 11
  • 24

2 Answers2

12

It might be a little late for an answer, but I've made it work after many attempts.


A few general considerations first:

  • As a general rule to follow (valid for node.js, too), you have to first make HTTPS work for WSS to work. WebSocket works over HTTP, so if your server has correctly configured HTTPS (or HTTP), then adding WebSocket to it will make WSS (or WS) work. Actually both HTTPS/WSS and HTTP/WS can work at the same time

  • Certificates are very important, as not all kinds of certificates work with Jetty (the same is true for node.js). So you have to generate the certificates that Jetty will accept.

  • Making HTTPS work is also important for self-signed certificates, as you might have to first access the server via HTTPS and add the exception before WSS can work (This might depend on the browser.)

  • Another thing to consider is that the context paths need to be correctly setup, too. I've made it work by having separate paths for HTTP and WS, like /hello for HTTP(S) and /ws for WS(S). It might be possible to do it with the same context path for both, but I didn't investigate that yet


Below are the steps I followed. I've put a working example on GitHub.

1) Generate the correct self-signed certificates by using the commands from here

The above link has an example on how to generate correct self-signed certificates. (If you have CA-signed certificates, the procedure is somewhat different, I think.)

I'm pasting the commands here for ease of access. Please pay attention to always give the same password whenever asked (including one of the final steps, when the password you type will appear in clear text on the screen).

openssl genrsa -aes256 -out jetty.key
openssl req -new -x509 -key jetty.key -out jetty.crt
keytool -keystore keystore -import -alias jetty -file jetty.crt -trustcacerts
openssl req -new -key jetty.key -out jetty.csr
openssl pkcs12 -inkey jetty.key -in jetty.crt -export -out jetty.pkcs12
keytool -importkeystore -srckeystore jetty.pkcs12 -srcstoretype PKCS12 -destkeystore keystore

2) Have the correct code for HTTPS in Jetty.

There are some resources on the web that show how to do HTTPS with Jetty, but for me only one worked, and it is here.

3) Have the correct code for handling contexts.

This one was tough - the example code from the Jetty documentation page did not work for me. What worked was this. This tutorial also enlightened me on the fact that I might have conflicts if I try to use the same path for HTTP and WS.

4) Finally, have the correct code for WebSocket

I've found correct WebSocket code here. The one that has what we need is native-jetty-websocket-example.

Community
  • 1
  • 1
riverhorse
  • 186
  • 2
  • 9
2

I'd like to add on to riverhorse's answer (I would add a comment, but at the time of writing I did not have the 50 required reputation to comment on answers).

While that answer shows how to complete the process with a self-signed key, I figured out how to do it with CA-signed certificates.

To do this I will assume you have access to 3 files which are the usual files you receive when purchasing a CA signed certificate

Note: you may have files like example.crt, example_key.txt, and all kinds of variations. That will not matter as long as there contents match the file they are meant to describe. In other words, all of the files are just text, so as long they contain the required text for that specific file, then you can use that file instead (or you can change the name).

example.cer -> the main certificate

example.key -> the key for the certificate

example.ca-bundle -> the intermediate certificate

The first step is to combine the main certificate and the intermediate into one file like so

Note that the order is important, the main certificate should be before the intermediate certificates

cat example.cer example.ca-bundle > cert-chain.txt

Now the next step is to use the example.key and the new cert-chain.txt to generate a pkcs12 file (which can then be put into the keystore). These steps are very similiar to riverhorse's answer.

so run

Every time it asks for a password, keep it the same, you will eventually use it in the code

openssl pkcs12 -export -inkey example.key -in cert-chain.txt -out example.pkcs12

The last step is to import into the keystore using

keytool -importkeystore -srckeystore example.pkcs12 -srcstoretype PKCS12 -destkeystore keystore

If you run into an issue like unable to load certificates 140126084731328:error:0908F066:PEM routines:get_header_and_data:bad end line:../crypto/pem/pem_lib.c:842 check the bottom of this answer for a potential fix that worked for me

Now you have the keystore for your jetty program and you can go back to riverhorse's example to see how that would be applied.

If you want to check that the keystore has the certificates you can run

keytool -list -v -keystore keystore > output_filename.txt

and then use cat output_filename.txt or nano output_filename.txt to see the contents.

These steps came from riverhorse's answer along with these 2 links below

https://www.thesslstore.com/knowledgebase/ssl-install/jetty-java-http-servlet-webserver-ssl-installation/

Really helpful in showing the overall steps, but I did not find success until combing it with the other link (however your mileage may vary as its very possible I made a mistake in my first try)

https://wiki.eclipse.org/Jetty/Howto/Configure_SSL#Requesting_a_Trusted_Certificate

Check the header "Loading Keys and Certificates via PKCS12", this link in general is somewhat outdated as using the openssl command to load the pkcs12 into the keystore was not a thing at this point, but it showed how to deal with intermediate certificates the best

Potential Fix to loading issue:

if you had this issue one possible thing that could of caused it is the cert-chain.txt file, open it and check that none of the lines between two certificates are like (5 before and after)

-----END CERTIFICATE----------BEGIN CERTIFICATE-----

as they should be

-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
kazar4
  • 77
  • 9