arduinoWebSockets
arduinoWebSockets copied to clipboard
secure websocket server support
will wss (http://tools.ietf.org/html/rfc6455#section-3) be supported later ?
kindly guide
thanks
wss as client is supported for the ESP8266 https://github.com/Links2004/arduinoWebSockets/blob/master/examples/WebSocketClientSSL/WebSocketClientSSL.ino#L81
saw that .. however WSS server is needed within esp8266 ... the reason is cross platform browser support for WSS. I checked your latest version with echo page of websockets.org .. below are the test results
- chrome - does not work
- firefox - does not work
- safari (MAC Yosemite) - works
- IE Edge (Win 10) - works
I think SSL is yet to be supported from https://github.com/esp8266/Arduino
Keep up the great work! Thanks
yes SSL/TLS server is not Implemented for the ESP8266 yet, if it get there I will integrated here too. but i think based on the limited Ram we then can only handle one or two connections.
true! By the way any idea why http://www.websocket.org/echo.html does not work in chrome & firefox ?
may because the websocket server will not send a Access-Control-Allow-Origin
header.
the same applies to ESP8266 hosted WebSocketServer ( example provided by you ) I used developer tools console to create a websocket client to connect to the ESP8266 Websock server. below are the test results for release 1.4
chrome - does not work firefox - does not work safari (MAC Yosemite) - works IE Edge (Win 10) - works
Can you add the response header into your source ?
will add it as option, since when its there its a possible security hole.
WebSocketsServer wsServer = WebSocketsServer(81, "*");
Thank you so much! Appreciate your prompt response
Sent from my Windows Phone
-----Original Message----- From: "Markus" [email protected] Sent: 1/14/2016 9:59 PM To: "Links2004/arduinoWebSockets" [email protected] Cc: "Chirag Ajmera" [email protected] Subject: Re: [arduinoWebSockets] secure websocket server support (#25)
WebSocketsServer wsServer = WebSocketsServer(81, "*"); — Reply to this email directly or view it on GitHub.
WebSocketsServer wsServer = WebSocketsServer(80, ""); WebSocketsServer wsServer = WebSocketsServer(9000, "");
chrome - works (both port 80 & 9000) - but with delayed initial connection establishment firefox - works with port 9000 & not with port 80
is there an example for having WSS server on the ESP8266 ? The ESP's now support SSL, however this lib does not support it yet.
@asmaklad Were you able to find a way to host a secure websocket server?
@shrynshjn No , never found a solution for it. They keep claiming it is not possible. But ESP8266 is now able to host an HTTPS server, someone has to develop the WSS protocol on top of it.
the real problem is you can't have a server HTTPS and WS out of the same server, modern browsers refuse mixed security webpages. HTML5 features such as GeoLocation are not possible to serve anymore without HTTPS.
I'm looking for this as well (in conjunction with (BearSSL::)ESP8266WebServerSecure
). For now I just run the regular HTTP ESP8266WebServer
but I'd like to have everything secured. :> I know I can set up a reverse proxy (or stunnel) somewhere but I prefer to have everything contained on the ESP itself.
I'm bumping this as well; I'd love to be able to run a secured server (I can provide the key/etc potentially via something like http://local-ip.co) from my ESP32 so I can connect to it from an HTTPS website.
Oh yeah I did figure out a way but forgot to mention it here. :> Note that this is specific to BearSSL::ESP8266WebServerSecure
and I'll assume you can already get that working on its own. It also requires support for web server hook functions but that shouldn't be a problem I think. Basically, instead of running WSS through a separate service, just let the web server handle and terminate the SSL connection. Pretty sure this also save a bunch of memory; SSL in general is fairly slow on ESPs but this setup definitely feels snappier than anything else I tried.
Since this library still expects working with plaintext-related objects it does need some changes. I simply copied WebSockets4WebServer.h
to WebSockets4WebServerSecure.h
and put it right next to my sketch. Its updated contents:
#ifndef __WEBSOCKETS4WEBSERVERSECURE_H
#define __WEBSOCKETS4WEBSERVERSECURE_H
#include <WebSocketsServer.h>
#include <ESP8266WebServerSecure.h>
#if WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266 && WEBSERVER_HAS_HOOK
class WebSockets4WebServerSecure : public WebSocketsServerCore {
public:
WebSockets4WebServerSecure(const String &origin = "", const String &protocol = "arduino") : WebSocketsServerCore(origin, protocol) {
begin();
}
// Original: ESP8266WebServer::HookFunction hookForWebserver(const String &wsRootDir, WebSocketServerEvent event) {
BearSSL::ESP8266WebServerSecure::HookFunction hookForWebserver(const String &wsRootDir, WebSocketServerEvent event) {
onEvent(event);
// Original: return [&, wsRootDir](const String &method, const String &url, WiFiClient *tcpClient, ESP8266WebServer::ContentTypeFunction contentType) {
// Can't change the 'WiFiClient' type here because that is passed in by BearSSL
return [&, wsRootDir](const String &method, const String &url, WiFiClient *tcpClient, BearSSL::ESP8266WebServerSecure::ContentTypeFunction contentType) {
(void)contentType;
WiFiClientSecure *tcpClientSecure = (WiFiClientSecure *)tcpClient; // Added
if(!(method == "GET" && url.indexOf(wsRootDir) == 0)) {
// Original: return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
return BearSSL::ESP8266WebServerSecure::CLIENT_REQUEST_CAN_CONTINUE;
}
// Allocate a WiFiClient copy (like in WebSocketsServer::handleNewClients())
// Original: WEBSOCKETS_NETWORK_CLASS *newTcpClient = new WEBSOCKETS_NETWORK_CLASS(*tcpClient);
WEBSOCKETS_NETWORK_SSL_CLASS *newTcpClient = new WEBSOCKETS_NETWORK_SSL_CLASS(*tcpClientSecure);
// Then initialize a new WSclient_t (like in WebSocketsServer::handleNewClient())
WSclient_t *client = handleNewClient(newTcpClient);
if(client) {
// Give "GET <url>"
String headerLine;
headerLine.reserve(url.length() + 5);
headerLine = "GET ";
headerLine += url;
handleHeader(client, &headerLine);
}
// Tell webserver to not close but forget about this client
// Original: return ESP8266WebServer::CLIENT_IS_GIVEN;
return BearSSL::ESP8266WebServerSecure::CLIENT_IS_GIVEN;
};
}
};
#else // WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266 && WEBSERVER_HAS_HOOK
#ifndef WEBSERVER_HAS_HOOK
#error Your current Framework / Arduino core version does not support Webserver Hook Functions
#else
#error Your Hardware Platform does not support Webserver Hook Functions
#endif
#endif // WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266 && WEBSERVER_HAS_HOOK
#endif // __WEBSOCKETS4WEBSERVERSECURE_H
For the #include
s in your sketch: add #include "WebSockets4WebServerSecure.h"
and remove all the other headers included from the original library. If you were already using hooked websockets (with a plaintext web server): the new header is a drop-in replacement for the original and you only need to change all instances of WebSockets4WebServer
to WebSockets4WebServerSecure
(and actually switch to HTTPS of course). Otherwise, to set it up:
BearSSL::ESP8266WebServerSecure my_web(443);
WebSockets4WebServerSecure my_wss;
void setup(void) {
// After setting up BearSSL but before calling it's begin() function
my_web.addHook(my_wss.hookForWebserver("/wss", websocket_handle_event)); // Added
my_web.begin(); // Now you can call this
}
See this repo's examples for the websocket_handle_event
function or other details. Then just connect to wss://your-web-server-name/wss
.
Note that if you have basic auth enabled for the main web server, you'll probably also need to enable it separately for websockets by putting my_wss.setAuthorization(user, pass)
below the my_web.begin()
call. I think it's due to working through a hook instead of my_web.on()
and the web server assumes you'll also handle the headers. Luckily the library already keeps track of that on its own though. =]
If you wanted you could also override some methods from the WebSocketsServerCore
class. I did that with handleNonWebsocketConnection
and handleAuthorizationFailed
to use different errors/realms. This way I can set the realm to the same thing the web server is using, causing browsers to automatically reuse that authentication for websockets too. That's probably what you'd want, so to override the functions just copy them verbatim into the new WebSockets4WebServerSecure
class, then edit them as needed.
I have tried implementing your solution, GottemHams. My webserver sockets are failing to connect though. Do I need to edit/create a WebSockets4WebServerSecure.cpp files as well as .h file?
@Joshua-Brack Do you have BearSSL in itself working? As in, can you run a web server and visit a page served from it? I'm asking because my example code doesn't actually set up BearSSL, I'm simply assuming people already have that part working at least.
Otherwise:
- Assuming you're using the Arduino IDE, do you have it set to support all SSL ciphers or just the basic set? Depending on your device it may simply run out of memory when using the full set. Perhaps check the serial console to see if it dumps a trace.
- Are you using
wss://
URLs to connect to the websocket?
Also no, you don't need an additional .cpp
file, the header is enough.
I have setup bearSSL for the webserver on my esp8266 using webserversecure. It works to the extent that it displays the website page without connecting sockets. Also I am using wss urls to connect to the socket. I added a println statement to monitor the free memory available and it remains adequate throughout the execution of the program. Inside chrome inspect element this is what I see:
Any ideas how to troubleshoot this? Also is that message suggesting that the socket connection is attempting to connect on port 83. If so that could be the problem. I specify port 443 everywhere that I use the wss url except in the server.addHook(webSocket.hookForWebserver("/wss", webSocketEvent)); line.
P.S. Thank you for the timely response and sacrifice of your time to help.
No problem. =] And nah the :83
just means the error happened on line 83 of your web page's code. Your websocket URL is incomplete though. You set the web server hook to /wss
, meaning to actually get to the websocket you'll need to connect to wss://192.168.255.162/wss
.
That fixed it! Thank you so much!
Is anyone able to provide a working example for this, I am trying to get it working but am not entirely sure how to go about it as some links provided are now broken too
@FallenRecruit this should be a full basic working example for the ESP side. Note though that I took some of my code from a larger project and condensed it to just web server/socket stuff, so it may not be 100% correct. :> I didn't include any of the browser-related JavaScript for interacting with websockets, because that's really a different topic altogether. I do mention the URL needed to connect to this specific example though, as well as an example for the ESP to receive and send text over the socket.
- For the libraries you'll need
BearSSL
(ESP8266WebServerSecure
in the library manager I think?) and (obviously)ESP8266WiFi
plusarduinoWebSockets
- Create a file
WebSockets4WebServerSecure.h
next to your sketch:
#ifndef __WEBSOCKETS4WEBSERVERSECURE_H
#define __WEBSOCKETS4WEBSERVERSECURE_H
#include <WebSocketsServer.h>
#include <ESP8266WebServerSecure.h>
// This is basically a copy of the original WebSockets4Webserver class, with additions to handle SSL =]
#if WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266 && WEBSERVER_HAS_HOOK
class WebSockets4WebServerSecure : public WebSocketsServerCore {
public:
WebSockets4WebServerSecure(const String &origin = "", const String &protocol = "arduino") : WebSocketsServerCore(origin, protocol) {
begin();
}
// Original: ESP8266WebServer::HookFunction hookForWebserver(const String &wsRootDir, WebSocketServerEvent event) {
BearSSL::ESP8266WebServerSecure::HookFunction hookForWebserver(const String &wsRootDir, WebSocketServerEvent event) {
onEvent(event);
// Original: return [&, wsRootDir](const String &method, const String &url, WiFiClient *tcpClient, ESP8266WebServer::ContentTypeFunction contentType) {
// Can't change the 'WiFiClient' type here because that is passed in by BearSSL
return [&, wsRootDir](const String &method, const String &url, WiFiClient *tcpClient, BearSSL::ESP8266WebServerSecure::ContentTypeFunction contentType) {
(void)contentType;
WiFiClientSecure *tcpClientSecure = (WiFiClientSecure *)tcpClient; // Added
if(!(method == "GET" && url.indexOf(wsRootDir) == 0)) {
// Original: return ESP8266WebServer::CLIENT_REQUEST_CAN_CONTINUE;
return BearSSL::ESP8266WebServerSecure::CLIENT_REQUEST_CAN_CONTINUE;
}
// Allocate a WiFiClient copy (like in WebSocketsServer::handleNewClients())
// Original: WEBSOCKETS_NETWORK_CLASS *newTcpClient = new WEBSOCKETS_NETWORK_CLASS(*tcpClient);
WEBSOCKETS_NETWORK_SSL_CLASS *newTcpClient = new WEBSOCKETS_NETWORK_SSL_CLASS(*tcpClientSecure);
// Then initialize a new WSclient_t (like in WebSocketsServer::handleNewClient())
WSclient_t *client = handleNewClient(newTcpClient);
if(client) {
// Give "GET <url>"
String headerLine;
headerLine.reserve(url.length() + 5);
headerLine = "GET ";
headerLine += url;
handleHeader(client, &headerLine);
}
// Tell webserver to not close but forget about this client
// Original: return ESP8266WebServer::CLIENT_IS_GIVEN;
return BearSSL::ESP8266WebServerSecure::CLIENT_IS_GIVEN;
};
}
};
#else // WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266 && WEBSERVER_HAS_HOOK
#ifndef WEBSERVER_HAS_HOOK
#error Your current Framework / Arduino core version does not support Webserver Hook Functions
#else
#error Your Hardware Platform does not support Webserver Hook Functions
#endif
#endif // WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266 && WEBSERVER_HAS_HOOK
#endif // __WEBSOCKETS4WEBSERVERSECURE_H
- Then for the actual sketch, I've added a bunch of comments to the relevant parts of the code because I think that's the easiest way to explain things:
#include <ESP8266WiFi.h>
//#include <ESP8266WebServerSecure.h> // Not necessary because it's included by WebSockets4WebServerSecure.h
//#include <WebSockets4WebServer.h> // Different version is implemented by WebSockets4WebServerSecure.h
#include "WebSockets4WebServerSecure.h"
// Using PROGMEM here to (better) accomodate large keys/certs, leaving more SRAM for actual processing (since PROGMEM makes it get stored in flash)
// All of these are simply in PEM format
static const char tls_key[] PROGMEM = R"EOF(
-----BEGIN PRIVATE KEY-----
.........PUT YOUR PRIVATE KEY HERE.........
-----END PRIVATE KEY-----
)EOF";
static const char tls_cert[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
.........PUT YOUR ENDPOINT CERTIFICATE HERE.........
-----END CERTIFICATE-----
)EOF";
// Depending on certificate setup, you may not need to pass along any intermediate or root cert, otherwise the format is the same
// If you do need these, then remember to uncomment the relevant lines in setup_http() too
/*static const char tls_cert_intermediate[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
.........PUT YOUR INTERMEDIATE CA CERTIFICATE HERE.........
-----END CERTIFICATE-----
)EOF";
static const char tls_cert_root[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
.........PUT YOUR ROOT CA CERTIFICATE HERE.........
-----END CERTIFICATE-----
)EOF";*/
BearSSL::ESP8266WebServerSecure myweb_server(443);
WebSockets4WebServerSecure mywebsocket_server;
void setup(void) {
Serial.begin(115200);
setup_wifi();
setup_http();
}
void loop(void) {
myweb_server.handleClient();
mywebsocket_server.loop();
}
void setup_wifi(void) {
unsigned short int i;
WiFi.mode(WIFI_STA);
WiFi.setHostname("espwebsocket"); // Not really necessary but I prefer explicit hostnames
WiFi.begin("wifissid", "supersecretpassword");
Serial.printf("Connecting to WiFi");
for(i = 0; i < 5 && WiFi.status() != WL_CONNECTED; i++) {
Serial.printf(".");
delay(1000);
}
Serial.printf("\n");
if(WiFi.status() != WL_CONNECTED) {
// Do something here to abort further execution (send an error somehow and shut down, go into deep sleep and retry X minutes later, etc)
}
}
void setup_http(void) {
BearSSL::X509List *tls_chain;
tls_chain = new BearSSL::X509List(tls_cert);
//tls_chain->append(tls_cert_intermediate);
//tls_chain->append(tls_cert_root);
myweb_server.getServer().setRSACert(tls_chain, new BearSSL::PrivateKey(tls_key));
// Register some pages
myweb_server.on("/", http_handle_root);
myweb_server.onNotFound(http_handle_404);
// And here is how we make the websocket pass through the actual server, so we can (re)use SSL and all that
myweb_server.addHook(mywebsocket_server.hookForWebserver("/socket", websocket_handle_event));
myweb_server.begin();
}
void http_handle_root(void) {
myweb_server.sendHeader("Cache-Control", "no-cache");
myweb_server.sendHeader("Connection", "close");
myweb_server.send(200, "text/html", "<!DOCTYPE html><html><body>Some HTML page where you can interact with the websocket</body></html>");
// The above HTML should contain some JavaScript for connecting to the socket, like this: new WebSocket('wss://' + document.location.host + '/socket');
// The '/socket' at the end corresponds to the value from the myweb_server.addHook() call
}
void http_handle_404(void) {
myweb_server.sendHeader("Cache-Control", "no-cache");
myweb_server.sendHeader("Connection", "close");
myweb_server.send(404, "text/html", "<!DOCTYPE html><html><body>Some HTML page indicating a 404 error</body></html>");
}
void websocket_handle_event(uint8_t client_id, WStype_t event, uint8_t *payload, size_t length) {
char *payload_chr;
IPAddress client_ip;
switch(event) {
case WStype_DISCONNECTED:
// Maybe log a message or something if you want
break;
case WStype_CONNECTED:
client_ip = mywebsocket_server.remoteIP(client_id);
// Maybe log a message or something if you want, to get the IP as string you can just printf("%d.%d.%d.%d", client_ip[0], client_ip[1], client_ip[2], client_ip[3])
break;
case WStype_TEXT:
if(!length) {
websocket_send_response(client_id, "Received an empty payload");
break;
}
payload_chr = (char *)payload; // Should be no problem since 'payload' is uint8_t ;]
websocket_send_response(client_id, "Received data: %s", payload_chr);
break;
case WStype_PING:
case WStype_PONG:
// Don't need to do anything, should be handled automatically afaik
break;
default:
// Maybe log a message or something if you want
break;
}
}
void websocket_send_response(uint8_t client_id, const char *pattern, ...) {
char buf[384]; // Anything too long will simply be truncated to fit
va_list vl;
va_start(vl, pattern);
vsnprintf(buf, sizeof(buf), pattern, vl);
va_end(vl);
mywebsocket_server.sendTXT(client_id, buf);
}