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
Related
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));
}
}
I am newbie. I cannot understand RMI correctly. There are tons of tutorials available on the internet ,but all of them are for the local host as I can understand. Both server and client run on the same machine.
I want to run client on any machine and the host will be on the one computer lets consider IP - 11.11.11.11. On the 1099.
But how can I achieve this, where should I specify my IP on the client side. As I understand naming convertion is used, like DNS but anyway when I need to connect to some machine remotely I need to know at least IP address (+mask) and port.
I guess I missed something really important.
Please give some example how to configure RMI remotly not on the same host.
First you have to setup a server whose method or object can be accessed by any remote client
Below is example code for the server.
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MyCalc extends Remote{
int add(int a, int b) throws RemoteException;
}
import java.rmi.RemoteException;
public class MyCalcImpl implements MyCalc {
#Override
public int add(int a, int b) throws RemoteException {
return (a + b);
}
}
Start the rmi registry on server machine so you can register your object to this registry and better you run it where you have placed your classes otherwise you will get ClassNotFound.
rmiregistry 1099
Note: you might need to change the port if port is already in use.
Register you object to rmi registry with name 'calculator'.
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
public class MyRMIServer {
public static void main(String[] args) throws Exception {
System.setProperty("java.security.policy","file:///tmp/test.policy");
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
try {
String name = "Calculator";
MyCalc engine = new MyCalcImpl();
MyCalc stub = (MyCalc) UnicastRemoteObject.exportObject(engine, 0);
Registry registry = LocateRegistry.getRegistry(1099);
System.out.println("Registering Calculator Object");
registry.rebind(name, stub);
} catch (Exception e) {
System.err.println("Exception:" + e);
e.printStackTrace();
}
}
}
Note: To run the program you have to setup a security policy file and for that creat a file e.g. test.policy and copy below content.
grant {
permission java.security.AllPermission;
permission java.net.SocketPermission "localhost:1099", "connect, resolve";
permission java.net.SocketPermission "127.0.0.1:1099", "connect, resolve";
permission java.net.SocketPermission "localhost:80", "connect, resolve";
};
You can change IP and port as per your case.
After starting the server, suppose your server's IP address is 11.11.11.11 then you can invoke the MyCalc's add() on the server. So on your client machine your client code would be like:
Copy the MyCalc class from server to client machine so you can set it to the classpath while compiling client's code.
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class MyRMIClient {
public static void main(String args[]) {
System.setProperty("java.security.policy","file:///tmp/test.policy");
if (System.getSecurityManager() == null) {
System.setSecurityManager(new SecurityManager());
}
try {
String name = "Calculator";
String serverIP = "11.11.11.11"; // or localhost if client and server on same machine.
int serverPort = 1099;
Registry registry = LocateRegistry.getRegistry(serverIP, serverPort);
MyCalc mycalc = (MyCalc) registry.lookup(name);
int result = mycalc.add(10, 20);
System.out.println("Result:" + result);
} catch (Exception e) {
System.err.println("ComputePi exception:");
e.printStackTrace();
}
}
}
compile and test the client's code.
EDIT: edited to remove dependency on rmi compiler (rmic)
You only have to specify the server's IP address in one place: the lookup string supplied to Naming.lookup().
[Unless you have the Linux problem referred to in the RMI FAQ item A.1.]
When I run the client it's supposed to send an email to my server and then I want my email server to print out the email details (to, from, port, message) to console. For some reason after running the client, nothing apparent happens on the server.
server
package example;
import org.subethamail.smtp.server.SMTPServer;
public class EmailServer {
public static void main(String[] args) {
MyMessageHandlerFactory myFactory = new MyMessageHandlerFactory();
SMTPServer smtpServer = new SMTPServer(myFactory);
smtpServer.setPort(25000);
smtpServer.start();
}
}
server output
run: [main] INFO org.subethamail.smtp.server.SMTPServer - SMTP server
*:25000 starting [org.subethamail.smtp.server.ServerThread *:25000] INFO org.subethamail.smtp.server.ServerThread - SMTP server *:25000
started
client
package example;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.subethamail.smtp.client.*;
public class EmailClient {
public static void main(String[] args) {
try {
SMTPClient sc = new SMTPClient();
sc.close();
sc.connect("localhost", 25000);
sc.sendReceive("test");
} catch (IOException ex) {
Logger.getLogger(EmailClient.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
client output
run: BUILD SUCCESSFUL (total time: 0 seconds)
Version is 3.1.7 from https://code.google.com/p/subethasmtp/downloads/list
The server requires MyMessageHandlerFactory which I copied from: https://code.google.com/p/subethasmtp/wiki/SimpleExample
OK, let's check the source code (always a good idea) and see what happens.
You send "test" via
SMTPClient sc;
sc.sendReceive("test"); // which is actually sent to your SMTPServer as "test\r\n"
Now, considering that this is a new SMTP conversation (see RFC5321 for everything you always wanted to know but were afraid to ask about such things) and "test" isn't a valid command VERB at this point in the conversation, you would expect to see an error returned by sendReceive().
But since you're ignoring the SMTPClient.Response#75 returned from what should have been
Response resp=SMTPClient.sendReceive()
you're missing out on both
resp.code (which I am sure is 500 - Permanent Negative Completion reply / Syntax - see the RFC above) and
resp.message describing the reason your command could not be fulfulled
both of which are returned from CommandHandler#93.
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");
}
}
I have some code to connect to the internet, it works fine in the simulator
but when I try it on a real device, I always get a 400 http response code
(the response body says "Connection timed out")
I'm using JRE 5
and using Blackberry 9000 on OS version 5 for both the real device and the simulator.
It is activated according to Advanced Options > Enterprise Activation
Is there something else I need to change on the real device to make it work?
I slowly whittled down my code to get to the root of the issue
and I'm down to this code:
package mypackage;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import net.rim.device.api.io.transport.TransportInfo;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.container.MainScreen;
public class MyApp extends UiApplication {
public static void main(String[] args) {
MyApp theApp = new MyApp();
theApp.enterEventDispatcher();
}
public MyApp() {
pushScreen(new SimpleScreen());
}
}
class SimpleScreen extends MainScreen {
public SimpleScreen() {
this.setTitle("Hello");
ConnectionThread ct = new ConnectionThread();
ct.start();
}
}
class ConnectionThread extends Thread {
private static String url = "http://www.wikipedia.org/";
public void run() {
System.out.println(" -- ConnectionThread.run()");
System.out.println(" ---- MDS hasSufficientCoverage? " + TransportInfo.hasSufficientCoverage(TransportInfo.TRANSPORT_MDS));
try {
HttpConnection httpConn;
httpConn = (HttpConnection) Connector.open(url);
final int iResponseCode = httpConn.getResponseCode();
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Dialog.alert("Response code: " + Integer.toString(iResponseCode));
}
});
} catch (Exception e) {
System.err.println("Caught IOException: " + e.getMessage());
}
System.out.println(" -- /ConnectionThread.run()");
}
}
==============================================
// EDIT:
I'm pretty sure its a device config issue now, I just the Network Diagnostic Tool and it also returns a 400 HTTP Response with the message "connect timed out".
==============================================
//EDIT #2:
I just tried options->mobile network->diagnostics test
Here are the results:
ICMP Ping Echo: No
------
Blackberry Registration: Yes
Connected to Blackberry: Yes
Blackberry PIN-PIN: Yes
------
Server Name: <my enterprise server>
Email Address: <my email>
Connected to <my email>: Yes
Then I tried options->mobile network->tools->ping
and pinged google and wikipedia and both say A network error occurred
Are you sure that your BB device is connected to any BES server? I would suggest that you first try to access internet from BB browser.
I asked the BES Admin and he says its Docomo issue.. and he has taken it up with them.
Basically there's no code problem (as far as I can tell)