javajettytyrus

Connecting to a secured websocket


I'm trying to connect to a secured websocket using Jetty (or any other library).

The issue is I get a "No trusted certificate found" error. I'm using a self-signed certificate generated with keytool. What could be done?

import java.net.URI;
import java.util.concurrent.Future;

import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.client.WebSocketClient;

public class Socket extends WebSocketAdapter{

    public static void main(String[] args) {
        String url = "wss://qa.sockets.stackexchange.com/"; //or "wss://echo.websocket.org"
        // making sure the the jvm find keystore
        String JAVASEC="C:/Program Files/Java/jdk1.8.0_25/jre/lib/security/";
        System.setProperty("javax.net.ssl.keyStore", JAVASEC+"keystore.jks");
        System.setProperty("javax.net.ssl.trustStore", JAVASEC+"cacerts.jks");
        System.setProperty("javax.net.ssl.keyStorePassword", "changeit");

        System.out.println(System.getProperty("javax.net.ssl.trustStore"));
        SslContextFactory sslContextFactory = new SslContextFactory();
        Resource keyStoreResource = Resource.newResource(Socket.class.getResource("/keystore.jks"));//generated with keytool
        sslContextFactory.setKeyStoreResource(keyStoreResource);
        sslContextFactory.setKeyStorePassword("password");
        sslContextFactory.setKeyManagerPassword("password");
        WebSocketClient client = new WebSocketClient(sslContextFactory);
        try{
            client.start();
            Socket socket = new Socket();
            Future<Session> fut = client.connect(socket,URI.create(url));
            Session session = fut.get();
            session.getRemote().sendString("Hello");
        }
        catch (Throwable t){
            t.printStackTrace(System.err);
        }
    }


    @Override
    public void onWebSocketConnect(Session sess){
        super.onWebSocketConnect(sess);
        System.out.println("Socket Connected: " + sess);
    }

    @Override
    public void onWebSocketText(String message){
        super.onWebSocketText(message);
        System.out.println("Received TEXT message: " + message);
    }

    @Override
    public void onWebSocketClose(int statusCode, String reason){
        super.onWebSocketClose(statusCode,reason);
        System.out.println("Socket Closed: [" + statusCode + "] " + reason);
    }

    @Override
    public void onWebSocketError(Throwable cause){
        super.onWebSocketError(cause);
        cause.printStackTrace(System.err);
    }
}

Here's an attempt with Tyrus websocket client, I don't get SSL error, but it prints nothing:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;

import javax.websocket.ClientEndpointConfig;
import javax.websocket.CloseReason;
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;

import org.glassfish.grizzly.ssl.SSLContextConfigurator;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.tyrus.client.ClientManager;
import org.glassfish.tyrus.container.grizzly.GrizzlyEngine;

public class ClientWebSocketEndpoint extends Endpoint {

    public static void main(String[] a) throws IOException{

        ClientManager client = ClientManager.createClient();

        //System.getProperties().put("javax.net.debug", "all");
        final SSLContextConfigurator defaultConfig = new SSLContextConfigurator();

        defaultConfig.retrieve(System.getProperties());
            // or setup SSLContextConfigurator using its API.

        SSLEngineConfigurator sslEngineConfigurator =
            new SSLEngineConfigurator(defaultConfig, true, false, false);
        client.getProperties().put(GrizzlyEngine.SSL_ENGINE_CONFIGURATOR,
            sslEngineConfigurator);
        Session session = null;
        final ClientEndpointConfig cec = ClientEndpointConfig.Builder.create().build();

        try {
            session = client.connectToServer(ClientWebSocketEndpoint.class, cec, new URI("wss://qa.sockets.stackexchange.com/"));// or "wss://echo.websocket.org"

        } catch (DeploymentException | URISyntaxException e) {
            e.printStackTrace();
        } finally {
            if (session != null && session.isOpen())
                session.close(new CloseReason(CloseReason.CloseCodes.GOING_AWAY, "Bye"));

        }

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        br.readLine();
    }

    @Override
    public void onOpen(Session session, EndpointConfig config) {
        session.addMessageHandler(new MessageHandler.Whole<String>() {

            @Override
            public void onMessage(String message) {
                System.out.println("Received message: "+message);
            }
        });
        try {
            session.getBasicRemote().sendText("1-questions-active");
            session.getBasicRemote().sendText("155-questions-active");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

In comparison this simple code in JS/node works

var WebSocket = require('ws')
  , ws = new WebSocket('wss://qa.sockets.stackexchange.com/');//"wss://echo.websocket.org"

ws.on('message', function(message) {
    console.log('received: %s', message);
});
ws.on('open', function() {
    ws.send('155-questions-active');
    ws.send('1-questions-active');
});

I'd be happy to know a working websocket client in Java


Solution

  • The correct way would be to configure your Client keystore / truststore to trust your specific self signed server certificate. There's plenty of answers on stackoverflow demonstrating how to do this with Java by simply configuring the keystore and/or truststore file contents appropriately. (no code necessary).

    If you want to "just connect", and you don't care about trust, certificate validity, or even endpoint identification validation (basically all of the good things that make up a well secured connection) then you don't need to mess with keystores, and truststores, or custom X509TrustManagers or even customized SSLContexts to connect to the service. This need is common during development/testing/qa time, but should not be used in production code.

    How you would do this with Jetty 9.4.35.v20201120 is to just configure the SslContextFactory.Client (that belongs to the HttpClient, which WebSocketClient is using) to trust all certificates.

    import java.net.URI;
    import java.util.concurrent.Future;
    
    import org.eclipse.jetty.client.HttpClient;
    import org.eclipse.jetty.util.log.Log;
    import org.eclipse.jetty.util.log.Logger;
    import org.eclipse.jetty.util.ssl.SslContextFactory;
    import org.eclipse.jetty.websocket.api.Session;
    import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
    import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
    import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
    import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
    import org.eclipse.jetty.websocket.api.annotations.WebSocket;
    import org.eclipse.jetty.websocket.client.WebSocketClient;
    
    @WebSocket
    public class SecureClientSocket
    {
        private static final Logger LOG = Log.getLogger(SecureClientSocket.class);
    
        public static void main(String[] args)
        {
            String url = "wss://qa.sockets.stackexchange.com/";
    
            SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
            // Tell sslContextFactory to trust all server certificates
            // This is suitable for test/qa environments, and internal environments,
            // but IS NOT SUITABLE FOR PRODUCTION.
            // Note: this is not actually necessary for wss://qa.sockets.stackexchange.com/
            sslContextFactory.setTrustAll(true);
            // If you do choose to comment out the above, this option will cause the
            // Java client side SSL/TLS to validate the server certificate name
            // against the URL used to connect to the server, if it doesn't match
            // then the connection is not established.
            sslContextFactory.setEndpointIdentificationAlgorithm("HTTPS");
    
            HttpClient httpClient = new HttpClient(sslContextFactory);
            try
            {
                httpClient.start();
                WebSocketClient client = new WebSocketClient(httpClient);
                client.start();
                SecureClientSocket socket = new SecureClientSocket();
                Future<Session> fut = client.connect(socket, URI.create(url));
                Session session = fut.get();
                session.getRemote().sendString("Hello");
                session.getRemote().sendString("155-questions-active");
            }
            catch (Throwable t)
            {
                LOG.warn(t);
            }
        }
    
        @OnWebSocketConnect
        public void onConnect(Session sess)
        {
            LOG.info("onConnect({})", sess);
        }
    
        @OnWebSocketClose
        public void onClose(int statusCode, String reason)
        {
            LOG.info("onClose({}, {})", statusCode, reason);
        }
    
        @OnWebSocketError
        public void onError(Throwable cause)
        {
            LOG.warn(cause);
        }
    
        @OnWebSocketMessage
        public void onMessage(String msg)
        {
            LOG.info("onMessage() - {}", msg);
        }
    }
    

    Which will have results like this ...

    2020-12-21 14:03:28.228:INFO::main: Logging initialized @173ms to org.eclipse.jetty.util.log.StdErrLog
    2020-12-21 14:03:28.401:WARN:oejusS.config:main: Trusting all certificates configured for Client@4b5a5ed1[provider=null,keyStore=null,trustStore=null]
    2020-12-21 14:03:28.402:WARN:oejusS.config:main: No Client EndPointIdentificationAlgorithm configured for Client@4b5a5ed1[provider=null,keyStore=null,trustStore=null]
    2020-12-21 14:03:28.862:INFO:j.SecureClientSocket:HttpClient@3712b94-60: onConnect(WebSocketSession[websocket=JettyAnnotatedEventDriver[jetty.SecureClientSocket@3f38289c],behavior=CLIENT,connection=WebSocketClientConnection@7556df66::DecryptedEndPoint@11c4a450{l=/192.168.1.217:46512,r=qa.sockets.stackexchange.com/198.252.206.25:443,OPEN,fill=-,flush=-,to=66/300000},remote=WebSocketRemoteEndpoint@278a12bf[batching=true],incoming=JettyAnnotatedEventDriver[jetty.SecureClientSocket@3f38289c],outgoing=ExtensionStack[queueSize=0,extensions=[],incoming=org.eclipse.jetty.websocket.common.WebSocketSession,outgoing=org.eclipse.jetty.websocket.client.io.WebSocketClientConnection]])
    2020-12-21 14:03:30.648:INFO:j.SecureClientSocket:HttpClient@3712b94-62: onMessage() - {"action":"155-questions-active","data":"{\"siteBaseHostAddress\":\"graphicdesign.stackexchange.com\",\"id\":138914,\"titleEncodedFancy\":\"How to achieve purple, paper background texture look?\",\"bodySummary\":\"How is the below purple background texture look achieved? I'm assuming it is a paper texture effect with different layers of light? However when I try to apply both the effect it never turns out the ...\",\"tags\":[\"texture\"],\"lastActivityDate\":1608581010,\"url\":\"https://graphicdesign.stackexchange.com/questions/138914/how-to-achieve-purple-paper-background-texture-look\",\"ownerUrl\":\"https://graphicdesign.stackexchange.com/users/155152/homan-cheung\",\"ownerDisplayName\":\"Homan Cheung\",\"apiSiteParameter\":\"graphicdesign\"}"}
    2020-12-21 14:03:30.791:INFO:j.SecureClientSocket:HttpClient@3712b94-60: onMessage() - {"action":"155-questions-active","data":"{\"siteBaseHostAddress\":\"unix.stackexchange.com\",\"id\":457386,\"titleEncodedFancy\":\"IPtables logging is not working in CentOS 7\",\"bodySummary\":\"I want to log all the traffic which comes in and out from the port X. I have followed below steps.\\r\\nEdited /etc/syslog.conf , /etc/rsyslog.conf , /etc/systemd/system/rsyslog.service.d/rsyslog.conf ...\",\"tags\":[\"centos\",\"kernel\",\"iptables\",\"logs\"],\"lastActivityDate\":1608581010,\"url\":\"https://unix.stackexchange.com/questions/457386/iptables-logging-is-not-working-in-centos-7\",\"ownerUrl\":\"https://unix.stackexchange.com/users/301499/karthikeyan-s\",\"ownerDisplayName\":\"Karthikeyan s\",\"apiSiteParameter\":\"unix\"}"}
    2020-12-21 14:03:32.235:INFO:j.SecureClientSocket:HttpClient@3712b94-62: onMessage() - {"action":"155-questions-active","data":"{\"siteBaseHostAddress\":\"math.stackexchange.com\",\"id\":812563,\"titleEncodedFancy\":\"Jacobian of exponential mapping in SO3/SE3\",\"bodySummary\":\"Following this post\\nJacobian matrix of the Rodrigues&#39; formula (exponential map)\\n\\nWhat if I really need the Jacobian of the exponential mapping function in $\\\\omega \\\\neq 0$?\\n\\nBasically, I want to ...\",\"tags\":[\"lie-groups\",\"3d\",\"rotations\",\"numerical-optimization\",\"rigid-transformation\"],\"lastActivityDate\":1608581012,\"url\":\"https://math.stackexchange.com/questions/812563/jacobian-of-exponential-mapping-in-so3-se3\",\"ownerUrl\":\"https://math.stackexchange.com/users/153816/user153816\",\"ownerDisplayName\":\"user153816\",\"apiSiteParameter\":\"math\"}"}
    2020-12-21 14:03:35.343:INFO:j.SecureClientSocket:HttpClient@3712b94-60: onMessage() - {"action":"155-questions-active","data":"{\"siteBaseHostAddress\":\"raspberrypi.stackexchange.com\",\"id\":76325,\"titleEncodedFancy\":\"GCC version for compiling a loadable kernel module\",\"bodySummary\":\"I am not very familiar with Linux, so may be my question is a little bit foolish.\\n\\nI would like to compile kernel module without recompiling the kernel (USB Wi-Fi stick driver for MT7106U chip). I ...\",\"tags\":[\"kernel\",\"modules\",\"gcc\"],\"lastActivityDate\":1608581015,\"url\":\"https://raspberrypi.stackexchange.com/questions/76325/gcc-version-for-compiling-a-loadable-kernel-module\",\"ownerUrl\":\"https://raspberrypi.stackexchange.com/users/77643/cyclone125\",\"ownerDisplayName\":\"cyclone125\",\"apiSiteParameter\":\"raspberrypi\"}"}
    2020-12-21 14:03:36.775:INFO:j.SecureClientSocket:JettyShutdownThread: onClose(1006, Disconnected)