How to write a secure websocket server in java desktop application - java

I have a Java Desktop Application, and this application starts a secure websocket server, written in Java. Right now it is using a self-signed certificate which requires the web browser user to manually accept the self-signed certificate to be able to start a client connection to the same websocket server.
I have a SSL certificate for a domain like "mydomain.com", and I'd like to use that SSL certificate to start the Java Desktop Application's secure websocket. Of course the computer will have to point that domain to the local machine, so I added 127.0.0.1 mydomain.com to my HOSTS file (I'm in Windows). Even so, it is not working.
So the question is:
Is it possible to create a Secure Websocket Server from this Java
Desktop Application, so that if my local browser tries to access
wss://mydomain.com/appservices it accepts the certificate as usual
(given the HOSTS file modification) ?, If this is possible, How can I
do that given the following example code?
Additional info and examples of current code
Here is the code that starts the secure websocket server in my Java Desktop App:
package com.mydomain.desktopclient.websockets.server;
import com.mydomain.desktopclient.resources.ResourceLoader;
import com.mydomain.desktopclient.websockets.server.endpoint.RequestServerEndpoint;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import javax.websocket.server.ServerContainer;
import java.net.URL;
public class SecureWebSocketServer {
Server server = null;
public void start() throws Exception {
server = new Server();
int httpsPort = 443;
URL keystore = ResourceLoader.getLocalResourceURL("/resources/misc/keystore/keystore.jks");
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath(keystore.toExternalForm());
sslContextFactory.setCertAlias("mydomain.com.key");
sslContextFactory.setKeyStorePassword("keystorepass");
sslContextFactory.setKeyManagerPassword("keymanagerpass");
sslContextFactory.addExcludeProtocols("SSLv3");
sslContextFactory.addExcludeCipherSuites(".*_GCM_.*");
HttpConfiguration httpsConf = new HttpConfiguration();
httpsConf.setSecurePort(httpsPort);
httpsConf.setSecureScheme("https");
httpsConf.addCustomizer(new SecureRequestCustomizer());
ServerConnector httpsConnector = new ServerConnector(server,
new SslConnectionFactory(sslContextFactory, "http/1.1"),
new HttpConnectionFactory(httpsConf));
httpsConnector.setHost("mydomain.com");
httpsConnector.setPort(httpsPort);
server.addConnector(httpsConnector);
HandlerList baseHandlers = new HandlerList();
server.setHandler(baseHandlers);
ServletContextHandler context = new ServletContextHandler();
// context.setVirtualHosts(new String[] {"mydomain.com"});
context.setContextPath("/");
baseHandlers.addHandler(context);
// Add WebSocket
ServerContainer jsrContainer = WebSocketServerContainerInitializer.configureContext(context);
jsrContainer.setDefaultMaxSessionIdleTimeout(1000 * 60 * 60 * 24);
jsrContainer.addEndpoint(RequestServerEndpoint.class);
Handler handler = new DefaultHandler();
baseHandlers.addHandler(handler);
server.setDumpAfterStart(true);
server.setDumpBeforeStop(true);
try {
server.start();
server.join();
} catch (Throwable t) {
t.printStackTrace(System.err);
}
}
public void stop() throws Exception {
if( server != null ){
server.stop();
server = null;
}
}
}
This code uses jetty-all-9.3.1.v20150714-uber.jar as websocket library.
The class com.mydomain.desktopclient.resources.ResourceLoader is a simple class to find and return files as resources from the app's jar file.
Here is the RequestServerEndpoint if needed:
package com.mydomain.desktopclient.websockets.server.endpoint;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.logging.Logger;
#ServerEndpoint(value = "/appservices")
public class RequestServerEndpoint {
private static Logger logger = Logger.getLogger(RequestServerEndpoint.class.getName());
#OnOpen
public void onOpen(Session session) {
logger.info("Connected ... " + session.getId());
}
#OnMessage
public String onMessage(String incommingMessage, Session session) {
logger.info("Received Message: \n\n" + incommingMessage);
}
#OnClose
public void onClose(Session session, CloseReason closeReason) {
logger.info(String.format("Session %s closed because of %s", session.getId(), closeReason));
}
}

Related

How to give certificate to Java Websocket?

Forgive me for the newb question, but I am confused and obviously not understanding the fundamentals or explanations of how to use a Websocket server hosted over HTTPS. Everything I find online leads me to have more questions than answers.
I have a Websocket server hosted on my HTTPS website using Java code.
This is my WebsocketServer.java file:
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Set;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class WebsocketServer extends WebSocketServer {
private static final Logger logger = LogManager.getLogger(WebsocketServer.class);
private static int TCP_PORT = 6868;
private static Set<WebSocket> conns;
public WebsocketServer() {
super(new InetSocketAddress(TCP_PORT));
conns = new HashSet<>();
}
#Override
public void onOpen(WebSocket conn, ClientHandshake handshake) {
conns.add(conn);
logger.info("New connection from " + conn.getRemoteSocketAddress().getAddress().getHostAddress());
logger.info("Size of connection list: " + conns.size());
}
#Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
conns.remove(conn);
logger.info("Closed connection to " + conn.getRemoteSocketAddress().getAddress().getHostAddress());
}
#Override
public void onMessage(WebSocket conn, String message) {
logger.info("Message from client: {}", message);
// for (WebSocket sock : conns) {
// sock.send("SENDING BACK" + message);
// }
}
#Override
public void onError(WebSocket conn, Exception ex) {
// ex.printStackTrace();
try {
if (conn != null) {
conns.remove(conn);
// do some thing if required
}
logger.info("ERROR from {}", conn.getRemoteSocketAddress().getAddress().getHostAddress());
} catch (Exception e) {
logger.info("onError: WebSocketServer may already be running");
}
}
public Set<WebSocket> getConns() {
return conns;
}
}
Then I started the WebsocketServer like this:
WebsocketServer websocketServer;
// Start socket server
websocketServer = new WebsocketServer();
websocketServer.start();
And on the client side, I connect to it like this:
// APP_WEB_SOCKET is the url to my site: api.my_custom_domain.com
var connection = new WebSocket("wss://" + APP_WEB_SOCKET + ":6868");
QUESTIONS:
I keep reading that I need a certificate if I want to use wss over HTTPS, but cannot find any documents that explain what this means in a way that I can understand.
My app is hosted in AWS Elastic Beanstalk environment. Do I need to somehow add a certificate to the setup of the WebsocketServer in my Java code?
Example:
WebsocketServer websocketServer;
// Start socket server
websocketServer = new WebsocketServer();
// example guessing
websocketServer.cert = "SOMETHING";??
websocketServer.start();
Does the client code need to be changed at all?
Who needs the certificate?
If someone could please explain what I am missing or point me in the correct direction, I would really appreciate it.
Keep it easy.
Certs inside your application are complex - they are hard to manage and you will get problems to run your application in a modern cloud environment (start new environments, renew certs, scale your application, ...).
Simple conclusion: Dont implement any certs.
How-to get encrypted connections?
As Mike already pointed out in the comments: WebSockets are just upgraded HTTP(S) connections. A normal webserver (nginx, apache) takes care about the certs. It can be done in kubernetes (as ingress-controller) or with a "bare-metal" webserver.
Both of them should act as a reverse-proxy. This means: Your java-application doesn't know anything about certs. It has just unencrypted connections - like in your code on port 6868.
But the client will not use this port. 6868 is only internally reachable.
The client will call your reverse-proxy at the normal HTTPS port (=443). The reverse-proxy will forward the connection to your java-application.
Here some links for further information:
nginx reverse-proxy
nginx reverse-proxy for websocket
tutorial for java behind reverse-proxy
LetsEncrypt for automatic and free certs

WebSphere admin client connection error

I am getting connection error on WebSphere admin client creation process.
I read many forums but cannot fix it.
"Exception creating Admin Client Connection: com.ibm.websphere.management.exception.ConnectorException: ADMC0016E: The system cannot create a SOAP connector to connect to host "111.xxxx.." at port 8879."
My dmgr port is 8879
Host name is "111.xxxx.."
Servers config files located c:\temp\soap.client.props, DummyClientTrustFile.jks, DummyClientKeyFile.jks
My code is below:
import java.util.Date;
import java.util.Properties;
import java.util.Set;
import javax.management.InstanceNotFoundException;
import javax.management.MalformedObjectNameException;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
import com.ibm.websphere.management.AdminClient;
import com.ibm.websphere.management.AdminClientFactory;
import com.ibm.websphere.management.exception.ConnectorException;
public class AdminClientConnection
{
private AdminClient adminClient;
public static void main(String[] args)
{
AdminClientConnection aClient = new AdminClientConnection();
// Create an AdminClient
aClient.createAdminClient();
}
private void createAdminClient()
{
// Set up a Properties object for the JMX connector attributes
Properties clientProps = new Properties();
clientProps.setProperty(
AdminClient.CONNECTOR_TYPE, AdminClient.CONNECTOR_TYPE_SOAP);
clientProps.setProperty(AdminClient.CONNECTOR_HOST, "111.xxxx..");
clientProps.setProperty(AdminClient.CONNECTOR_PORT, "8879");
clientProps.setProperty(AdminClient.CONNECTOR_SECURITY_ENABLED, "true");
clientProps.setProperty(AdminClient.USERNAME, "usr");
clientProps.setProperty(AdminClient.PASSWORD, "pass");
clientProps.setProperty(AdminClient.CONNECTOR_SOAP_CONFIG, "c:/temp/soap.client.props");
clientProps.setProperty("javax.net.ssl.trustStore", "c:/temp/DummyClientTrustFile.jks");
clientProps.setProperty("javax.net.ssl.keyStore", "c:/temp/DummyClientKeyFile.jks");
clientProps.setProperty("javax.net.ssl.trustStorePassword", "WebAS");
clientProps.setProperty("javax.net.ssl.keyStorePassword", "WebAS");
// Get an AdminClient based on the connector properties
try
{
adminClient = AdminClientFactory.createAdminClient(clientProps);
}
catch (ConnectorException e)
{
System.out.println("Exception creating Admin Client Connection: " + e);
System.exit(-1);
}
System.out.println("Connected to Application Server");
}
}
Be sure that CONNECTOR_PORT is true, CONNECTOR_SECURITY_ENABLED is not neccassary. Be sure that soap.client.props and jks files are gathered from the connector host.

Jetty 8.1.1 Websocket client handshake

I'm using Jetty 8.1.1 Websocket client api.
I need to update the headers with ("Sec-WebSocket-Protocol", "xsCrossfire") and ("Authorization", "Basic TLVWQMZqRr2hasYnZoI=")
WebSocketClientFactory factory = new WebSocketClientFactory();
factory.start();
client = factory.newWebSocketClient();
client.getCookies().put("Sec-WebSocket-Protocol", "xsCrossfire");
client.getCookies().put("Authorization", "Basic TLVWQMZqRr2hasYnZoI=");
Future<Connection> conn = client.open(uri, (WebSocket) this);
System.out.printf("Connecting to : %s%n", uri);
request looks:
Host: iltlvl262:8000\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Version: 13\r\n
Sec-WebSocket-Key: FHKTsICO2vqGCxXVwLkH4Q==\r\n
Cookie: Sec-WebSocket-Protocol=xsCrossfire\r\n
Cookie: Authorization="Basic TLVWQMZqRr2hasYnZoI="\r\n
expected request:
Host: iltlvl262:8000\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Version: 13\r\n
Sec-WebSocket-Key: FHKTsICO2vqGCxXVwLkH4Q==\r\n
Sec-WebSocket-Protocol: xsCrossfire\r\n
Authorization: "Basic TLVWQMZqRr2hasYnZoI="\r\n
how do I implement handshake in version 8.1.1 correctly?
Some good news and some bad news.
First, the Good news:
To set the Sec-WebSocket-Protocol header use the following.
client.setProtocol("xsCrossfire");
before you use client.open()
Next, the Bad news:
With Jetty 8.x you cannot set arbitrary non-websocket headers. This was due to how early experimental drafts of WebSocket were written. You simply were not allowed to set arbitrary headers per the early draft specs, so the implementation back in the Jetty 8.x days just didn't allow it.
However, with the finalization of RFC6455 (the official WebSocket spec) things changed, all of those changes were rolled into the Jetty 9.x codebase. Which is 100% RFC6455 compliant. (Note: Jetty 8 is 100% compliant to RFC6455 on the server side. Jetty 8 is also 100% compatible on the RFC6455 protocol use for both server and client. However, Jetty 8 is only partially compliant on the client side, from a features and API point of view.)
The decision with Jetty 7 and Jetty 8 was made to keep the old experimental drafts around for those early adopters and old browsers (Safari 5.x) that still used them. This decision prevented us from allowing behaviors that are specifically prevented in the old experimental drafts.
Starting with Jetty 9.x all old experimental drafts of websocket were dropped, leaving only RFC6455 to support, which allowed Jetty to open up more features that were previously disallowed. This includes arbitrary headers on the WebSocketClient.
Example of Jetty 9.1 WebSocket Client
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.Future;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
public class ExampleClient
{
public static class ExampleSocket extends WebSocketAdapter
{
#Override
public void onWebSocketText(String message)
{
try
{
// echo the message
getRemote().sendString(message);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
try
{
new ExampleClient().demo();
}
catch (Throwable t)
{
t.printStackTrace(System.err);
}
}
public void demo() throws Exception
{
WebSocketClient client = new WebSocketClient();
try
{
client.start();
ClientUpgradeRequest request = new ClientUpgradeRequest();
request.setSubProtocols("xsCrossfire");
request.setHeader("Authorization","Basic TLVWQMZqRr2hasYnZoI=");
URI wsUri = URI.create("ws://iltlvl262:8000/echo");
ExampleSocket socket = new ExampleSocket();
Future<Session> future = client.connect(socket,wsUri,request);
future.get(); // wait for connect
socket.getRemote().sendString("hello"); // send message
}
finally
{
client.stop();
}
}
}
Also note, that starting with Jetty 9.1, even the javax.websocket (JSR-356) API is fully supported.
Same example using javax.websocket on Jetty 9.1
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.websocket.ClientEndpoint;
import javax.websocket.ContainerProvider;
import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
public class ExampleClient
{
#ClientEndpoint(subprotocols = { "xsCrossfire" },
configurator = ExampleClient.Configurator.class)
public static class ExampleSocket
{
#OnMessage
public String onMessage(String msg)
{
return msg; // echo
}
}
public static class Configurator
extends javax.websocket.ClientEndpointConfig.Configurator
{
#Override
public void beforeRequest(Map<String, List<String>> headers)
{
List<String> authvalues = new ArrayList<>();
authvalues.add("Basic TLVWQMZqRr2hasYnZoI=");
headers.put("Authorization", authvalues);
super.beforeRequest(headers);
}
}
public static void main(String[] args)
{
try
{
new ExampleClient().demo();
}
catch (Throwable t)
{
t.printStackTrace(System.err);
}
}
public void demo() throws Exception
{
WebSocketContainer client = ContainerProvider.getWebSocketContainer();
ExampleSocket socket = new ExampleSocket();
URI wsUri = URI.create("ws://iltlvl262:8000/echo");
Session session = client.connectToServer(socket,wsUri);
session.getAsyncRemote().sendText("Hello");
}
}

Deploy simple server code to Heroku

I recently visited heroku.com site and tried to deploy my first java program there , I actually had a good start using their java deployment tutorial, and had it run ok. now I have a server code which I need to deploy there , I tried to follow the example but I had some question in mind like,
1- what will be the host in this case , I already tried the app link as if its the host but it throws errors ,
here is my sample server code
public class DateServer {
/** Runs the server. */
public static void main(String[] args) throws IOException {
ServerSocket listener = new ServerSocket(6780);
try {
while (true) {
Socket socket = listener.accept();
try {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println(new Date().toString());
} finally {
socket.close();
}
}
} finally {
listener.close();
}
}
}
here is my client code
public class DateClient {
/** Runs the client as an application. First it displays a dialog box asking for the IP address or hostname of a host running the date server, then connects to it and displays the date that it serves. */
public static void main(String[] args) throws IOException {
//I used my serverAddress is my external ip address
Socket s = new Socket(serverAddress, 6780);
BufferedReader input = new BufferedReader(new InputStreamReader(s.getInputStream()));
String answer = input.readLine();
JOptionPane.showMessageDialog(null, answer);
System.exit(0);
}
}
I followed this tutorial https://devcenter.heroku.com/articles/java at their site to upload my server code is there something else I need to do ?!
thanks in advance
On Heroku, your application must bind to the HTTP port provided in the $PORT environment variable. Given this, the two major problems in your application code above are 1) you are binding to a hardcoded port (6780) and 2) your application is using TCP instead of HTTP. As shown in the tutorial, use something like Jetty to accomplish the HTTP equivalent of your application and use System.getenv("PORT") to bind to the right port, like this:
import java.util.Date;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.*;
public class HelloWorld extends HttpServlet {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().print(new Date().toString());
}
public static void main(String[] args) throws Exception{
Server server = new Server(Integer.valueOf(System.getenv("PORT")));
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
context.addServlet(new ServletHolder(new HelloWorld()),"/*");
server.start();
server.join();
}
}

Create WebSphere administrative client program for java

I am using IBM WebSphere server. I need to Create WebSphere administrative client program for java using WebSphere Administrative API's. I am using this code for creating admin client
...
adminClient = AdminClientFactory.createAdminClient(connectProps);
...
but it gives exception.
The system cannot create a SOAP connector to connect to host localhost at port 8881.
After creating client I want to configure WASADMIN through this API. Am I on right track?
I need to get shared library through this API.
check if you have this server SOAP connector port set to 8881.
In Dmgr click the server name than Ports to check it. If not using 8881 than change it to the correct port being used by the server you're trying to connect to.
Update:
I did a test in my environment(Linux) and the following code worked( I had to add WebSphere_ND8.5/AppServer/runtimes/com.ibm.ws.admin.client_8.5.0.jarto classpath to run it without getting a ClassNotFoundException):
import java.util.Properties;
import java.util.logging.Logger;
import com.ibm.websphere.management.AdminClient;
import com.ibm.websphere.management.AdminClientFactory;
import com.ibm.websphere.management.exception.ConnectorException;
public class AdminConnect {
private static Logger logger = Logger.getLogger(AdminConnect.class.getName());
/**
* #param args
*/
public static void main(String[] args) {
Properties connectProps = new Properties();
connectProps.setProperty(
AdminClient.CONNECTOR_TYPE, AdminClient.CONNECTOR_TYPE_SOAP);
connectProps.setProperty(AdminClient.CONNECTOR_HOST, "localhost");
connectProps.setProperty(AdminClient.CONNECTOR_PORT, "8880");
// connectProps.setProperty(AdminClient.USERNAME, "test2");
// connectProps.setProperty(AdminClient.PASSWORD, "user24test");
AdminClient adminClient = null;
try
{
adminClient = AdminClientFactory.createAdminClient(connectProps);
logger.info("Connected successfuly with WebSphere :) ");
}
catch (ConnectorException e)
{
logger.severe("Exception creating admin client: " + e);
e.printStackTrace();
}
}
}

Categories

Resources