From 2f71d0e1388c002cc45c0d314cbc648d1a17478c Mon Sep 17 00:00:00 2001 From: amandel Date: Sat, 3 Dec 2022 17:34:02 +0100 Subject: [PATCH 1/3] Remove legacy sample code --- extras/legacy/README.md | 417 -------------------------- extras/legacy/https_server.cpp | 524 --------------------------------- extras/legacy/https_server.h | 48 --- 3 files changed, 989 deletions(-) delete mode 100644 extras/legacy/README.md delete mode 100644 extras/legacy/https_server.cpp delete mode 100644 extras/legacy/https_server.h diff --git a/extras/legacy/README.md b/extras/legacy/README.md deleted file mode 100644 index 4684753..0000000 --- a/extras/legacy/README.md +++ /dev/null @@ -1,417 +0,0 @@ -# ESP32 HTTPS Server - -**THIS IS AN OLD VERSION OF THE README / EXAMPLE SKETCH** that was valid before -the repository has been converted to follow the Arduino Library structure. - -This repository contains an HTTPS server implementation that can be used with the [ESP32 Arduino Core](https://github.com/espressif/arduino-esp32). - -The main goals for the server are: -- Ability to handle multiple clients in parallel (works, but memory limits it to 3-4 clients) -- Possibility to be executed in a task without interrupting the main program (works) -- Ability to handle `Connection: keep-alive` (works), SSL Session Reuse (works) and WebSockets (tbd, see [Issue 13](https://github.com/fhessel/esp32_https_server/issues/9)) to reduce the SSL-overhead for streaming data operations -- Provide a simple user interface for request handling (works) and middleware functionality (tbd, see [Issue 10](https://github.com/fhessel/esp32_https_server/issues/10)) - -For the future, the project structure will be converted to serve as a regular Arduino Library. For the moment, you may just -copy the https folder into your project, it is self-contained and needs only the WiFi-Library. - -## Setup instructions - -You have two options how you can use this repository: - -- Run the server with some example code showing its functionality (probably a good start) -- Running the server with your own code - -### Running the example - -The repository currently contains the HTTPS library as well as a sample scripts that shows how the server can be set up and used. The project has been developed using [Sloeber](http://eclipse.baeyens.it/), so it should be possible to import the repository folder as a project there, if you want to run the example. - -Furthermore, you need to do some setup after cloning the repository: - -- The example needs to connect to your local Wifi and therefor need credentials. For convenience, `data/wifi/wifi.example.h` contains a code skeleton for that. Just copy it to `/data/wifi/wifi.h` and enter your SSID and WPA2 PSK. -- Besides encryption, one main reason to use HTTP with TLS is to authenticate the server. So the server needs a certificate. If you just want to try the code, the repository contains a script to create a basic self-signed certificate. Note that this will be shown as insecure in your browser and you need parameters to allow such certificates if you access your server using command line tools like `openssl s_client`. -You can run `tools/cert/create_cert.sh` to create header files with certificate data in `data/cert/`. In a real-world setup, you should create those certificates more carefully. - -The sketch in `https_server.cpp` shows by example how to use various functions of the library. After compiling and flashing it to your ESP32 module, you should be able to access the following resources on the ESP32 via HTTPS: - -- your-ip/ - Welcome page showing the uptime and some parameterized SVG-images -- your-ip/favicon.ico - Favicon that is stored in data/favicon.h -- /images/awesome.svg?color=de58fe - SVG-Image, using the optional parameter as background color -- /echo - Will return request body for POST and PUT requests -- /param/[a]/[b] - Will show the parsed parameters a and b -- Any OPTIONS request - Will return some CORS-headers -- Everything else - JSON error object - -### Just use the library for your own project - -The library is self-contained, so you should be fine by just copying the `https/` folder into your project. Include it into your sketch as follows: - -```C++ -// Inlcudes for setting up the server -#include "https/HTTPSServer.hpp" - -// Includes to define request handler callbacks -#include "https/HTTPRequest.hpp" -#include "https/HTTPResponse.hpp" - -// Easier access to the classes of the server -using namespace httpsserver -``` - -For details on the API, refer to the following chapter. - -## Usage - -While there are many classes in the `https` folder, you will most likely only need to use three of them directly. - -In general, you will set up an **HTTPS server** that is listening on a specific port and handling incoming connections. To tell the server what it should do if it is hit by a request, you will define **request-handler-functions** that are bound to specific URLs. The server will parse the HTTP request, retrieve the matching handler function (if one has been registered for that URL) and than call that function. - -### Setting up the server - -The minimal code to start the server could look like this: - -```C++ -// Include the server class to your sketch -#include "https/HTTPSServer.hpp" - -// Use the HTTPS server namespace -using namespace httpsserver; - -// Create certificate data (see tools/cert on how to create it) -SSLCert cert = SSLCert( - crt_DER, // DER-formatted certificate data - crt_DER_len, // length of the certificate data - key_DER, // private key for that certificate - key_DER_len // Length of the private key -); - -// Setup the server with default configuration and the -// certificate that has been specified before -HTTPSServer myServer = HTTPSServer(cert); - -// (Configure request-handler-functions here) - -// Open socket and start processing -myServer.start(); - -``` - -In a basic setup, you also need to pass control to the server regularly so that it can handle incoming connections or data. To do so, add the following line to your `loop()` function: - -```C++ -myServer.loop(); -``` - -That's basically all that you need to do to have a server listening on port 443 and handling incoming requests. To add actual functionality, you need also to define handler functions to answer to requests. - -#### HTTPSServer - -This is a short overview on the public API of the `HTTPSServer` class. - -**Constructor** - -Creates a new server. - -```C++ -HTTPSServer(SSLCert * cert, const uint16_t port = 443, const uint8_t maxConnections = 4, const in_addr_t bindAddress = 0) -``` - -- _cert_ is the certificate data (instance of `SSLCert`) that will be used by the server to authenticate itself -- _port_ is the port that the server will listen on. Defaults to 443 -- _maxConnections_ is the maximum amount of incoming connections that will be served simultaneously. Due to memory limitations, four are the maximum that the ESP will be able to handle, and depending on your code it might be required to lower this number. If more connections reach the server at the same time, the server will only `accept` as many as configured and let other connections wait until resources are freed or a client-side timeout occurs. -- _bindAddress_ is the network interface address that the server should bind to. Defaults to 0 = all interfaces. Possible use case: The ESP is simultaneously in AP mode and STA mode, but the server should only be available in one network. - -**loop()** - -Processes requests, needs to be called periodically. - -```C++ -loop() -``` - -This function first checks for new incoming connections and accepts them if there are resources available (see maxConnections parameter of the constructor). Then, control is passed to the open connections to process received data or send data to the client. If a matching handler-function is defined, it will be called now. - -To decouple the server loop from your actual application, you can run it in a separate task on your ESP32. See below for an example on how to do it. - -**start()** - -Starts the server. - -```C++ -server.start() -``` - -This function will start the server. It will create the socket and SSL resource, tie everything together and open the port defined in the constructor call on the interface defined in the constructor call. - -**stop()** - -Stops the server. - -```C++ -server.stop() -``` - -Will try to close every open connection to the server, and then shut the server down and free socket and SSL resources. The call is blocking until every connection has been teared down successfully. You can start the server again afterwards. - -**isRunning()** - -Returns `true` if the server is running. If you called `start()` and this function returns `false` afterwards, the server could not be started. - -```C++ -server.isRunning() -``` - -**setDefaultHeader()** - -Adds a HTTP header that will be set in every response. - -```C++ -server.setDefaultHeader(std::string name, std::string value) -``` - -- _name_ The name of the header that should be set -- _value_ The value that this header should have - -An example use case would be to add [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) headers to every request. - -Note that you can also set headers in handler functions dynamically. - -**registerNode()** - -Adds a resource URL to the server. - -```C++ -server.registerNode(ResourceNode *node) -``` - -- _node_ The node that should be made available on the server - -The specified node will be made available on the server, so that the linked handler function will be called, if the URL specified in the node is requested. - -Note that URL matching is done in the order that the nodes are added to the server. This becomes important if you use wildcard-URLs. If you for example want to have the URLs `/items/new` and `/items/*` (with * being an ID), you'll have to register the non-wildcard node first (and the wildcard-node will never get `*=new` as parameter value). - -**unregisterNode()** - -Removes a resource URL from the server - -```C++ -server.unregisterNode(ResourceNode *node) -``` - -- _node_ The node that should no longer be available - -The resource is removed from the server so that it can no longer be accessed by requesting the specified resource URL. Note that you have to pass the exact same instance of `ResourceNode` that you also passed to `registerNode()`, otherwise the node cannot be removed. - -**setDefaultNode()** - -Registers a node as the default node. - -```C++ -server.setDefaultNode(ResourceNode *node) -``` - -- _node_ The `ResourceNode` that should act as default node. - -The default node is called when no other node matches the requested URL. This is a good way to implement a 404-page. The URL that is configured in the `ResourceNode` is ignored when it is configured as default node. - -#### Running the Server in Background - -(tbd, see the sample code in https_server.cpp and https_server.h for reference) - -### Defining Request-Handler-Functions - -Request-handler-functions add actual functionality to the server. They have the following signature: - -```C++ -handlerFunction(HTTPRequest * req, HTTPResponse * res) -``` - -They are called once the request line (URL and HTTP method) and the HTTP headers are received completely and can handle an optional request body. They get two parameters: - -- _req_ The request data. This object allows access to the request headers, method, URL, URL parameters and the request body. -- _res_ The response data. This object allows to write to the response body, set response headers and the status code. - -To define when a specific function should be called, it has to be bound with a URL (pattern) and an HTTP method to form a `ResourceNode`. This node can than be passed to the `HTTPSServer`'s `registerNode()` function to become available. One handler function may be bound to multiple `ResourceNode`s to serve various URLs. - -#### ResourceNode - -The `ResourceNode` connects a handler function, a request URL and an HTTP method. - -**Constructor** - -The constructor creates a (immutable) `ResourceNode`: - -```C++ -ResourceNode(const std::string path, const std::string method, const HTTPSCallbackFunction * callback) -``` - -- _path_ The request URL that the node should match, for example `/about` to be available under `https://10.0.0.10/about`. May also contain wildcards for some parts of the path, like `/item/*` to match `/item/1` or `/item/abc`. Currently, only full path segments are supported for the `*` wildcard (so you can do `item/*/*` but not `item/a-*` and `item/b-*`). -- _method_ The HTTP method, like `GET` or `POST`. The server does not check if you use valid HTTP methods, so if you really need it, you can get creative here. -- _callback_ The handler function, with a signature as mentioned above. - -Request parameters (`/items?sort=ASC`) are allowed for every URL and are made available to the handler function. - -See `HTTPRequest` on how to access the wildcard/request parameters. - -#### HTTPRequest - -The `HTTPRequest` object allows to access request information from within the handler function. - -**getHeader()** - -Returns the value of an HTTP request header, if it is set - -```C++ -req.getHeader(std::string name); -``` - -- _name_ The name of the header - -**readChars()** - -Reads the request body (or a part of it, if it is not fully received yet) into a char buffer. Useful if you expect textual requests (like JSON data). - -```C++ -readChars(char * buffer, size_t length); -``` - -- _buffer_ The buffer to read into (starting at index 0) -- _length_ The length of the buffer -- Returns the length that has actually been read into that buffer - -**readBytes** - -Like `readChars()` but with an byte array (if you expect binary data). - -```C++ -size_t readBytes(byte * buffer, size_t length); -``` - -(see `readChars()`) - -**getContentLength()** - -Returns the request body length. - -```C++ -getContentLength(); -``` - -- Returns the length of the request body - -**requestComplete()** - -Checks whether the whole request body has been read. - -```C++ -requestComplete(); -``` - -- Returns true, if the whole request body has been read. - -Note that every request-handler-function should read the whole body of the request. If it does not, a warning will be logged by the server, as not parsing the whole body is probably a runtime error. - -**discardRequestBody** - -Drop the request body without parsing it. - -```C++ -discardRequestBody(); -``` - -This will just push the whole request body to the trash. It can be used to discard the body if you did not expect a body to be present but one has been received (misbehaving client). - -**getParams()** - -Returns the resource parameters for the resource node. - -```C++ -getParams(); -``` - -- Returns a `ResourceParameters` instance that allows access to the values of wildcards in a URL or request parameters. See below for details. - -#### ResourceParamters - -This object can be accessed via the `HTTPRequest` and allows to retrieve parameter values from the URL. _Request parameters_ are the optional parameters following the question mark in a URL (accessed by name), _URL parameters_ are the wildcard parameters defined when creating a resource node (accessed by index). - -**isRequestParameterSet()** - -Check whether a request parameter is set. - -```C++ -isRequestParameterSet(std::string &name); -``` - -- Returns true if the parameter is set. - -**getRequestParameter() / getRequestParameterInt()** - -Returns the value of a request parameter. - -```C++ -getRequestParameter(std::string &name); -getRequestParameterInt(std::string &name); -``` -- _name_ The name of the parameter to retrieve - -The second function is just for convenience and does string to integer conversion for you. - -**getUrlParameter() / getUrlParameterInt()** - -Returns the value of a URL parameter. - -```C++ -getUrlParameter(uint8_t idx); -getUrlParameterInt(uint8_t idx); -``` - -- _idx_ The index of the request parameter, starting with 0 - -The second function is just for convenience and does string to integer conversion for you. - -#### HTTPResponse - -The response object allows to send data back to the client. - -It also implements Arduino's `Print` interface, so you can use it very similar to well-known classes like `Serial` etc. to write to the request body. - -**setStatusCode() / setStatusText** - -Allows to set the HTTP status information. - -```C++ -setStatusCode(uint16_t statusCode); -``` - -- _statusCode_ The code to be returned, like `404` - -```C++ -setStatusText(std::string statusText); -``` - -- _statusText_ The text to be returned, like "Not found" - -You also need to write this to the body if you want it to be shown in the browser. - -**setHeader()** - -Sets a header for this response. - -```C++ -void setHeader(std::string name, std::string value); -``` - -- _name_ the name of the header field, like `Content-Type` -- _value_ the value for that header field, like `application/json` - -Multiple headers with the same name are not supported. You may concatenate multiple values with a semicolon and pass them to this function once, though. - -**isHeaderWritten()** - -Checks whether the header has already been sent to the client - -```C++ -isHeaderWritten(); -``` - -Returns true if the header or parts of it have been sent to the client. Modification of the header values or the HTTP status is no longer possible then. diff --git a/extras/legacy/https_server.cpp b/extras/legacy/https_server.cpp deleted file mode 100644 index 446dc59..0000000 --- a/extras/legacy/https_server.cpp +++ /dev/null @@ -1,524 +0,0 @@ -// Do not remove the include below -#include "https_server.h" - -using namespace httpsserver; - -#define HEADER_USERNAME "X-USERNAME" -#define HEADER_GROUP "X-GROUP" - -/** - * This callback will be linked to the web server root and answer with - * a small HTML page that shows the uptime of the esp. - * - * Note that the headers may be transmitted as soon as the first data output - * is sent. So calls to setStatusCode, setStatusText or setHeader should be - * made before the print functions are called. - * - * HTTPResponse implements Print, so it can be used very similar to other - * Arduino classes like Serial/... - */ -void testCallback(HTTPRequest * req, HTTPResponse * res) { - res->setStatusCode(200); - res->setStatusText("OK"); - res->setHeader("Content-Type", "text/html; charset=utf8"); - - res->println(""); - res->println(""); - res->println(""); - // test if connection is encrypted - res->println(req->isSecure() ? "HTTPS Server on ESP32" : "HTTP Server on ESP32"); - res->println(""); - res->println(""); - res->println(req->isSecure() ? "

Hello HTTPS world!

" : "

Hello HTTP world!

"); - res->println("

... from your ESP32

"); - // The image resource is created in the awesomeCallback some lines below - res->println("\"Awesome"); - res->println("\"Awesome"); - res->println("\"Awesome"); - res->print("

System has been up for "); - res->print((int)(millis()/1000), DEC); - res->println(" seconds.

"); - res->println("

Login

"); - res->println(""); - res->println(""); -} - - - -/** - * The URL Param Callback demonstrates the usage of placeholders in the URL. - * - * This callback function is mapped to "param/ * / *" (ignore the spaces, they are required - * because of the C comment syntax). - * - * The placeholder values can be accessed through HTTPRequest::getParams. They are indexed - * beginning from 0. - */ -void urlParamCallback(HTTPRequest * req, HTTPResponse * res) { - // Get access to the parameters - ResourceParameters * params = req->getParams(); - - // Set a simple content type - res->setHeader("Content-Type", "text/plain"); - - // Print the first parameter - res->print("Parameter 1: "); - res->printStd(params->getUrlParameter(0)); - - // Print the second parameter - res->print("\nParameter 2: "); - res->printStd(params->getUrlParameter(1)); -} - -/** - * This callback responds with an SVG image to a GET request. The icon is the awesome smiley. - * - * If the color request parameter is set (so the URL is like awesome.svg?color=fede58), the - * background of our awesome face is changed. - */ -void awesomeCallback(HTTPRequest * req, HTTPResponse * res) { - // Get access to the parameters - ResourceParameters * params = req->getParams(); - - // Set SVG content type - res->setHeader("Content-Type", "image/svg+xml"); - - // Check if there is a suitabel fill color in the parameter: - std::string fillColor = "fede58"; - - // Get request parameter - std::string colorParamName = "color"; - if (params->isRequestParameterSet(colorParamName)) { - std::string requestColor = params->getRequestParameter(colorParamName); - if (requestColor.length()==6) { - bool colorOk = true; - for(int i = 1; i < 6 && colorOk; i++) { - if (!( - (requestColor[i]>='0' && requestColor[i]<='9' ) || - (requestColor[i]>='a' && requestColor[i]<='f' ) - )) { - colorOk = false; - } - } - if (colorOk) { - fillColor = requestColor; - } - } - } - - // Print the data - // Source: https://commons.wikimedia.org/wiki/File:718smiley.svg - res->print(""); - res->print(""); - res->print(""); - res->print("printStd(fillColor); - res->print("\"/>"); - res->print(""); - res->print(""); - res->print(""); - res->print(""); - res->print(""); - res->print(""); - res->print(""); - res->print(""); -} - -/** - * This callback is configured to match all OPTIONS requests (see pattern configuration below) - * - * This allows to define headers there that are required to allow cross-domain-xhr-requests, - * which enabled a REST-API that can be used on the esp32, while the WebInterface is hosted - * somewhere else (on a host with more storage space to provide huge JS libraries etc.) - * - * An example use case would be an IoT dashboard that connects to a bunch of local esp32s, - * which provide data via their REST-interfaces that is aggregated in the dashboard. - */ -void corsCallback(HTTPRequest * req, HTTPResponse * res) { - res->setHeader("Access-Control-Allow-Methods", "HEAD,GET,POST,DELETE,PUT,OPTIONS"); - res->setHeader("Access-Control-Allow-Origin", "*"); - res->setHeader("Access-Control-Allow-Headers", "*"); -} - -/** - * This callback simply copies the requests body into the response body. - * - * It can be used to test POST and PUT functionality and is configured to reply to - * POST /echo and PUT /echo - */ -void echoCallback(HTTPRequest * req, HTTPResponse * res) { - res->setHeader("Content-Type","text/plain"); - byte buffer[256]; - while(!(req->requestComplete())) { - size_t s = req->readBytes(buffer, 256); - res->write(buffer, s); - } -} - -/** - * This callback belongs to the authentication example. It is the user/password - * protected page visible as /internal and has a user-specific greeting. - */ -void internalCallback(HTTPRequest * req, HTTPResponse * res) { - res->setStatusCode(200); - res->setStatusText("OK"); - res->setHeader("Content-Type", "text/html; charset=utf8"); - res->println(""); - res->println(""); - res->println(""); - res->println("Internal Area"); - res->println(""); - res->println(""); - res->print("

Hello "); - // We can safely use the header value, this area is only accessible if it's set. - res->printStd(req->getHeader(HEADER_USERNAME)); - res->print("!

"); - res->println("

Welcome to the internal area. Congratulations to successfully entering your password!

"); - // The "admin area" will only be shown if the correct group has been assigned in the authenticationMiddleware - if (req->getHeader(HEADER_GROUP) == "ADMIN") { - res->println("
"); - res->println("

You are an administrator

"); - res->println("

If this was more than a simple example, you could do crazy things here.

"); - res->println("

Go to secret admin page

"); - res->println("
"); - } - res->println("

Go back home

"); - res->println(""); - res->println(""); -} - -/** - * This resource callback also has limited access, but it manages it manually instead of letting the middleware do all the stuff. - */ -void internalAdminCallback(HTTPRequest * req, HTTPResponse * res) { - res->setHeader("Content-Type", "text/html; charset=utf8"); - std::string header = "Secret Admin Page

Secret Admin Page

"; - std::string footer = ""; - // Checking permissions can not only be done centrally in the middleware function but also in the actual request handler. - // This would be handy if you provide an API with lists of resources, but access rights are defined object-based. - if (req->getHeader(HEADER_GROUP) == "ADMIN") { - res->setStatusCode(200); - res->setStatusText("OK"); - res->printStd(header); - res->println("
"); - res->println("

Congratulations

"); - res->println("

You found the secret administrator page!

"); - res->println("

Go back

"); - res->println("
"); - } else { - res->printStd(header); - res->setStatusCode(403); - res->setStatusText("Unauthorized"); - res->println("

403 Unauthorized You have no power here!

"); - } - res->printStd(footer); -} - -/** - * This callback will be registered as default callback. The default callback is used - * if no other node matches the request. - * - * Again, another content type is shown (json). - */ -void notfoundCallback(HTTPRequest * req, HTTPResponse * res) { - // Discard the request body, as the 404-handler may also be used for put and post actions - req->discardRequestBody(); - res->setStatusCode(404); - res->setStatusText("Not found"); - res->setHeader("Content-Type", "application/json"); - - res->print("{\"error\":\"not found\", \"code\":404}"); -} - -/** - * The loggingMiddleware is an example for a middleware function. It will be called for every - * request, but before the request is passed to the actual handler function. It may just do - * some generic functions like logging, but it may also modify the request and response directly. - * - * Additionally to the Request and Response parameters that are similar to the request handler - * function, it also gets a function pointer to a next() function. Only if next is called, - * the request handler function will be called. It is also possible to chain multiple middleware - * functions using this, handing over control step by step. - * - * Not calling the next() function will not handle the request even though it might be configured in - * a resource node. This allows functionality like access control. - * - * Make sure to place your code correctly before, after or around the next() call. In this example, - * we want to log the request method, the request url, login user name and the status code. The first - * two bits of information are available from the request, but the status code is only set after the - * response is finished and the username header is set by a middleware function later in the chain. - * So we have to place our logging call below the next() function. - */ -void loggingMiddleware(HTTPRequest * req, HTTPResponse * res, std::function next) { - next(); - Serial.printf("loggingMiddleware: %3d\t%s\t%s\t%s\n", - res->getStatusCode(), - req->getMethod().c_str(), - req->getHeader(HEADER_USERNAME).length() > 0 ? req->getHeader(HEADER_USERNAME).c_str() : "NOBODY", - req->getRequestString().c_str()); -} - -/** - * The following middleware function is one of two functions dealing with access control. The - * authenticationMiddleware will interpret the HTTP Basic Auth header, check usernames and password, - * and if they are valid, set the X-USERNAME and X-GROUP header. - * - * If they are invalid, the X-USERNAME and X-GROUP header will be unset. This is important because - * otherwise the client may manipulate those internal headers. - * - * From then on, further middleware functions and the request handler functions will be able to just - * use req->getHeader("X-USERNAME") to find out if the user is logged in correctly. - * - * Furthermore, if the user supplies credentials and they are invalid, he will receive an 403 response - * without any other functions being called. - */ -void authenticationMiddleware(HTTPRequest * req, HTTPResponse * res, std::function next) { - // Unset both headers to discard any value from the client - req->setHeader(HEADER_USERNAME, ""); - req->setHeader(HEADER_GROUP, ""); - // Get login information from request - std::string reqUsername = req->getBasicAuthUser(); - std::string reqPassword = req->getBasicAuthPassword(); - // If the user entered login information, we will check it - if (reqUsername.length() + reqPassword.length() > 0) { - // _Very_ simple user database - bool authValid = true; - std::string group = ""; - if (reqUsername == "admin" && reqPassword == "secret") { - group = "ADMIN"; - } else if (reqUsername == "user" && reqPassword == "test") { - group = "USER"; - } else { - authValid = false; - } - // If authentication was successful - if (authValid) { - // set custom headers and delegate control - req->setHeader(HEADER_USERNAME, reqUsername); - req->setHeader(HEADER_GROUP, group); - next(); - } else { - // Display error page - res->setStatusCode(401); - res->setStatusText("Unauthorized"); - res->setHeader("Content-Type", "text/plain"); - // This should trigger the browser user/password dialog: - res->setHeader("WWW-Authenticate", "Basic realm=\"ESP32 privileged area\""); - res->println("401. Unauthorized (try admin/secret or user/test)"); - } - } else { - // Otherwise just let the request pass through - next(); - } -} - -/** - * This function plays together with the authenticationMiddleware. While the first function checks the - * username/password combination and stores it in the request, this function makes use of this information - * to allow or deny access. - * - * This example only prevents unauthorized access to every ResourceNode stored under an /internal/... path. - */ -void authorizationMiddleware(HTTPRequest * req, HTTPResponse * res, std::function next) { - std::string username = req->getHeader(HEADER_USERNAME); - // Check that only logged-in users may get to the internal area (All URLs starting with /internal - // Only a simple example, more complicated configuration is up to you. - if (username == "" && req->getRequestString().substr(0,9) == "/internal") { - // Same as above - res->setStatusCode(401); - res->setStatusText("Unauthorized"); - res->setHeader("Content-Type", "text/plain"); - res->setHeader("WWW-Authenticate", "Basic realm=\"ESP32 privileged area\""); - res->println("401. Unauthorized (try admin/secret or user/test)"); - } else { - // Everything else will be allowed. - next(); - } -} - -//The setup function is called once at startup of the sketch -void setup() -{ - Serial.begin(115200); - Serial.println("setup()"); - - // Setup wifi. We use the configuration stored at data/wifi/wifi.h - // If you don't have that file, make sure to copy the wifi.example.h, - // rename it and also configure your wifi settings there. - Serial.print("Connecting WiFi"); - WiFi.begin(WIFI_SSID, WIFI_PSK); - while (WiFi.status() != WL_CONNECTED) { - Serial.print("."); - delay(100); - } - Serial.print(" connected. IP="); - Serial.println(WiFi.localIP()); - - // Setup the server as a separate task. - // - // Important note: If the server is launched as a different task, it has its own - // stack. This means that we cannot use globally instantiated Objects there. - // -> Make sure to create Server, ResourceNodes, etc. in the function where they - // are used (serverTask() in this case). - // Another alternative would be to instantiate the objects on the heap. This is - // especially important for data that should be accessed by the main thread and - // the server. - Serial.println("Creating server task... "); - // If stack canary errors occur, try to increase the stack size (3rd parameter) - // or to put as much stuff as possible onto the heap (ResourceNodes etc) - // 4096 byte _should_ suffice for the https server only, but with the http server - // running in the same task, that's not enough for stable operation, so we use - // a bit more here. - xTaskCreatePinnedToCore(serverTask, "https443", 6144, NULL, 1, NULL, ARDUINO_RUNNING_CORE); - - Serial.println("Beginning to loop()..."); -} - -// The loop function is called in an endless loop -void loop() { - // Use your normal loop without thinking of the server in the background - - // Delay for about five seconds and print some message on the Serial console. - delay(5 * 1000); - - // We use this loop only to show memory usage for debugging purposes - uint32_t freeheap = ESP.getFreeHeap(); - Serial.printf("Free Heap: %9d \n", freeheap); -} - -/** - * As mentioned above, the serverTask method contains the code to start the server. - * - * The infinite loop in the function is the equivalent for the loop() function of a - * regular Arduino sketch. - */ -void serverTask(void *params) { - Serial.println("Configuring Server..."); - - // Define the certificate that should be used - // See files in tools/cert on how to create the headers containing the certificate. - // Because they are just byte arrays, it would also be possible to store and load them from - // non-volatile memory after creating them on the fly when the device is launched for the - // first time. - SSLCert cert = SSLCert( - example_crt_DER, example_crt_DER_len, - example_key_DER, example_key_DER_len - ); - - // The faviconCallback now is assigned to the /favicon.ico node, when accessed by GET - // This means, it can be accessed by opening https://myesp/favicon.ico in all - // web browsers. Most browser fetch this file in background every time a new webserver - // is used to show the icon in the tab of that website. - ResourceNode * faviconNode = new ResourceNode("/favicon.ico", "GET", &faviconCallback); - - // The awesomeCallback is very similar to the favicon. - ResourceNode * awesomeNode = new ResourceNode("/images/awesome.svg", "GET", &awesomeCallback); - - // A simple callback showing URL parameters. Every asterisk (*) is a placeholder value - // So, the following URL has two placeholders that have to be filled. - // This is especially useful for REST-APIs where you want to represent an object ID in the - // url. Placeholders are arbitrary strings, but may be converted to integers (Error handling - // is up to the callback, eg. returning 404 if there is no suitable resource for that placeholder - // value) - ResourceNode * urlParamNode = new ResourceNode("/param/*/*", "GET", &urlParamCallback); - - // The echoCallback is configured on the path /echo for POST and PUT requests. It just copies request - // body to response body. To enable it for both methods, two nodes have to be created: - ResourceNode * echoNodePost = new ResourceNode("/echo", "POST", &echoCallback); - ResourceNode * echoNodePut = new ResourceNode("/echo", "PUT", &echoCallback); - - // The root node (on GET /) will be called when no directory on the server is specified in - // the request, so this node can be accessed through https://myesp/ - ResourceNode * rootNode = new ResourceNode("/", "GET", &testCallback); - - // As mentioned above, we want to answer all OPTIONS requests with a response that allows - // cross-domain XHR. To do so, we bind the corsCallback to match all options request - // (we can exploit the asterisk functionality for this. The callback is not required to - // process the parameters in any way.) - // Note the difference to the "/" in the rootNode above - "/" matches ONLY that specific - // resource, while slash and asterisk is more or less provides a catch all behavior - ResourceNode * corsNode = new ResourceNode("/*", "OPTIONS", &corsCallback); - - // Those two nodes belong to the middleware authentication and authorization example. - // They are protected if no user/password is given (see authenticationMiddleware and authorizationMiddleware - // for details. - ResourceNode * internalNode = new ResourceNode("/internal", "GET", &internalCallback); - ResourceNode * internalAdminNode = new ResourceNode("/internal/admin", "GET", &internalAdminCallback); - - - // The not found node will be used when no other node matches, and it's configured as - // defaultNode in the server. - // Note: Despite resource and method string have to be specified when a node is created, - // they are ignored for the default node. However, this makes it possible to register another - // node as default node as well. - ResourceNode * notFoundNode = new ResourceNode("/", "GET", ¬foundCallback); - - // Create the SSL server. The constructor takes some optional parameters, eg. to specify the TCP - // port that should be used. However, defining a certificate is mandatory. - HTTPSServer secureServer = HTTPSServer(&cert); - - // We also create a default HTTP server without encryption on port 80 - HTTPServer insecureServer = HTTPServer(); - - // We put references to both servers in an array so we can configure them in a loop (as we are lazy). - // Note that you can use the same ResourceNode on multiple servers! - HTTPServer * serverList[] = {&secureServer, &insecureServer}; - for(int i = 0; i < 2; i++) { - HTTPServer * server = serverList[i]; - - // Register the nodes that have been configured on the web server. - server->setDefaultNode(notFoundNode); - server->registerNode(rootNode); - server->registerNode(faviconNode); - server->registerNode(awesomeNode); - server->registerNode(urlParamNode); - server->registerNode(echoNodePost); - server->registerNode(echoNodePut); - server->registerNode(corsNode); - server->registerNode(internalNode); - server->registerNode(internalAdminNode); - - // Add a default header to the server that will be added to every response. In this example, we - // use it only for adding the server name, but it could also be used to add CORS-headers to every response - server->setDefaultHeader("Server", "esp32-http-server"); - - // Add all middleware functions to the server. Order is important! - server->addMiddleware(&loggingMiddleware); - server->addMiddleware(&authenticationMiddleware); - server->addMiddleware(&authorizationMiddleware); - } - - // The web server can be start()ed and stop()ed. When it's stopped, it will close its server port and - // all open connections and free the resources. Theoretically, it should be possible to run multiple - // web servers in parallel, however, there might be some restrictions im memory. - Serial.println("Starting HTTP Server..."); - insecureServer.start(); - Serial.println("Starting HTTPS Server..."); - secureServer.start(); - - // We check whether the server did come up correctly (it might fail if there aren't enough free resources) - if (insecureServer.isRunning() && secureServer.isRunning()) { - - // If the server is started, we go into our task's loop - Serial.println("Servers started."); - while(1) { - // Run the server loop. - // This loop function accepts new clients on the server socket if there are connection slots available - // (see the optional parameter maxConnections on the HTTPSServer constructor). - // It also frees resources by connections that have been closed by either the client or the application. - // Finally, it calls the loop() function of each active HTTPSConnection() to process incoming requests, - // which will finally trigger calls to the request handler callbacks that have been configured through - // the ResourceNodes. - insecureServer.loop(); - secureServer.loop(); - delay(1); - } - } else { - // For the sake of this example, we just restart the ESP in case of failure and hope it's getting better - // next time. - Serial.println("Starting Server FAILED! Restart in 10 seconds"); - delay(10000); - ESP.restart(); - } - -} diff --git a/extras/legacy/https_server.h b/extras/legacy/https_server.h deleted file mode 100644 index 8129a92..0000000 --- a/extras/legacy/https_server.h +++ /dev/null @@ -1,48 +0,0 @@ -// Only modify this file to include -// - function definitions (prototypes) -// - include files -// - extern variable definitions -// In the appropriate section - -#ifndef _https_server_H_ -#define _https_server_H_ - -// Arduino libraries -#include "Arduino.h" - -// For multitasking -#if CONFIG_FREERTOS_UNICORE -#define ARDUINO_RUNNING_CORE 0 -#else -#define ARDUINO_RUNNING_CORE 1 -#endif - -// We will use wifi -#include - -// Copy data/wifi/wifi.example.h to /data/wifi/wifi.h and change the ssid and psk -#include "data/wifi/wifi.h" - -// Run tools/cert/create_cert.sh to create a CA and issue a certificate that will -// be stored in the data/cert/ header files -#include "data/cert/cert.h" -#include "data/cert/private_key.h" - -// The favicon (binary) -#include "data/favicon.h" - -// Inlcudes for setting up the server -#include "src/HTTPSServer.hpp" - -// Includes to define request handler callbacks -#include "src/HTTPRequest.hpp" -#include "src/HTTPResponse.hpp" - -// The server loop will be configured as a separate task, so the server will run -// independently from all other code. -// As an alternative, it's possible to call the HTTPServer::loop() function in the -// main loop after setting up the server in setup(). -// The loop function triggers all connection handling etc. in the background -void serverTask(void * params); - -#endif /* _https_server_H_ */ From 05c4a06e6d7381bddc1ca926b8cd78fd7a6dd8b8 Mon Sep 17 00:00:00 2001 From: amandel Date: Sun, 7 Aug 2022 16:02:29 +0200 Subject: [PATCH 2/3] New ESP_IDF version, compile error for logger macro --- src/WebsocketHandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WebsocketHandler.cpp b/src/WebsocketHandler.cpp index c0c3cc9..b17cb7a 100644 --- a/src/WebsocketHandler.cpp +++ b/src/WebsocketHandler.cpp @@ -21,7 +21,7 @@ static void dumpFrame(WebsocketFrame frame) { case WebsocketHandler::OPCODE_TEXT: opcode = std::string("TEXT"); break; } ESP_LOGI( - TAG, + "HTTP", "Fin: %d, OpCode: %d (%s), Mask: %d, Len: %d", (int)frame.fin, (int)frame.opCode, From 256db3f1419d61cf40768c7f53b1941005307316 Mon Sep 17 00:00:00 2001 From: Frank Hessel Date: Sat, 23 Jan 2021 04:33:18 +0100 Subject: [PATCH 3/3] self-signed certificates: Add CN as subjectAltName --- CHANGELOG.md | 8 ++++++ src/SSLCert.cpp | 70 +++++++++++++++++++++++++++++++++++++++++++++++++ src/SSLCert.hpp | 3 +++ 3 files changed, 81 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b48fbf..4e4b273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## [v2.0.0](https://github.com/jackjansen/esp32_idf5_https_server/tree/master) + +– For self-signed certificates generated on the ESP32, the CN is now added as subjectAltName + +Breaking changes: + +– Generating self-signed certificates requires now a `CN=` as part of the distinguished name of the subject + ## [v1.1.1](https://github.com/jackjansen/esp32_idf5_https_server/tree/master) - Addressed issues with esp32-arduino-core v3 and esp-idf 5.0 diff --git a/src/SSLCert.cpp b/src/SSLCert.cpp index fe608fd..a01a5eb 100644 --- a/src/SSLCert.cpp +++ b/src/SSLCert.cpp @@ -1,5 +1,12 @@ #include "SSLCert.hpp" +#include + +#ifndef HTTPS_DISABLE_SELFSIGNING +#include +#include +#endif + namespace httpsserver { SSLCert::SSLCert(unsigned char * certData, uint16_t certLength, unsigned char * pkData, uint16_t pkLength): @@ -53,6 +60,56 @@ void SSLCert::clear() { #ifndef HTTPS_DISABLE_SELFSIGNING +/** + * Returns the CN value from a DN, or "" if it cannot be found + */ +static std::string get_cn(std::string dn) { + size_t cnStart = dn.find("CN="); + if (cnStart == std::string::npos) { + return ""; + } + cnStart += 3; + size_t cnStop = dn.find(",", cnStart); + if (cnStop == std::string::npos) { + cnStop = dn.length(); + } + return dn.substr(cnStart, cnStop - cnStart); +} + +/** + * Sets the DN as subjectAltName extension in the certificate + */ +static int add_subject_alt_name(mbedtls_x509write_cert *crt, std::string &cn) { + size_t bufsize = cn.length() + 8; // some additional space for tags and length fields + uint8_t buf[bufsize]; + uint8_t *p = &buf[bufsize - 1]; + uint8_t *start = buf; + int length = 0; + int ret; // used by MBEDTLS macro + + // The ASN structure that we will construct as parameter for write_crt_set_extension is as follows: + // | 0x30 = Sequence | length | 0x82 = dNSName, context-specific | length | cn0 | cn1 | cn2 | cn3 | .. | cnn | + // ↑ : ↑ `-------------v------------------´: + // | : `-------------------´ : + // | `----------v------------------------------------------------------------------´ + // `---------------´ + // Let's encrypt has useful infos: https://letsencrypt.org/docs/a-warm-welcome-to-asn1-and-der/#choice-and-any-encoding + MBEDTLS_ASN1_CHK_ADD(length, + mbedtls_asn1_write_raw_buffer(&p, start, (uint8_t*)cn.c_str(), cn.length())); + MBEDTLS_ASN1_CHK_ADD(length, + mbedtls_asn1_write_len(&p, start, length)); + MBEDTLS_ASN1_CHK_ADD(length, + mbedtls_asn1_write_tag(&p, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | 0x02)); // 0x02 = dNSName + MBEDTLS_ASN1_CHK_ADD(length, + mbedtls_asn1_write_len(&p, start, length)); + MBEDTLS_ASN1_CHK_ADD(length, + mbedtls_asn1_write_tag(&p, start, MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE )); + return mbedtls_x509write_crt_set_extension( crt, + MBEDTLS_OID_SUBJECT_ALT_NAME, MBEDTLS_OID_SIZE(MBEDTLS_OID_SUBJECT_ALT_NAME), + 0, // not critical + p, length); +} + /** * Function to create the key for a self-signed certificate. * @@ -174,6 +231,12 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom char dn_cstr[dn.length()+1]; strcpy(dn_cstr, dn.c_str()); + // Get the common name for the subject alternative name + std::string cn = get_cn(dn); + if (cn == "") { + return HTTPS_SERVER_ERROR_CERTGEN_CN; + } + // Initialize the entropy source mbedtls_entropy_init( &entropy ); @@ -233,6 +296,13 @@ static int cert_write(SSLCert &certCtx, std::string dn, std::string validityFrom goto error_after_cert; } + // Set subject alternative name + stepRes = add_subject_alt_name( &crt, cn ); + if (stepRes != 0) { + funcRes = HTTPS_SERVER_ERROR_CERTGEN_NAME; + goto error_after_cert; + } + #if MBEDTLS_VERSION_NUMBER >= 0x03000000 mbedtls_x509write_crt_set_serial_raw(&crt, (unsigned char *) serial, strlen(serial)); #else diff --git a/src/SSLCert.hpp b/src/SSLCert.hpp index 6296c3d..2fc9909 100644 --- a/src/SSLCert.hpp +++ b/src/SSLCert.hpp @@ -27,6 +27,7 @@ #define HTTPS_SERVER_ERROR_CERTGEN_NAME 0x17 #define HTTPS_SERVER_ERROR_CERTGEN_SERIAL 0x18 #define HTTPS_SERVER_ERROR_CERTGEN_VALIDITY 0x19 +#define HTTPS_SERVER_ERROR_CERTGEN_CN 0x1a #endif // !HTTPS_DISABLE_SELFSIGNING @@ -165,6 +166,8 @@ enum SSLKeySize { * would be: * CN=myesp.local,O=acme,C=US * + * The subjectAltName is extracted from the CN component of the distinguished name. + * * The strings validFrom and validUntil have to be formatted like this: * "20190101000000", "20300101000000" *