Skip to content

Commit

Permalink
Add a 'esp_https_server' component allowing to use http_server with O…
Browse files Browse the repository at this point in the history
…penSSL
  • Loading branch information
MightyPork committed Oct 31, 2018
1 parent 2efda16 commit 4947ff4
Show file tree
Hide file tree
Showing 15 changed files with 651 additions and 0 deletions.
7 changes: 7 additions & 0 deletions components/esp_https_server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
set(COMPONENT_ADD_INCLUDEDIRS include)
set(COMPONENT_SRCS "src/https_server.c")

set(COMPONENT_REQUIRES esp_http_server openssl)
set(COMPONENT_PRIV_REQUIRES lwip)

register_component()
44 changes: 44 additions & 0 deletions components/esp_https_server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# HTTPS server

This component is built on top of `esp_http_server`. The HTTPS server takes advantage of hooks and
function overrides in the regular HTTP server to provide encryption using OpenSSL.

All documentation for `esp_http_server` applies also to a server you create this way.

## Used APIs

The following API of `esp_http_server` should not be used with `esp_https_server`, as they are
used internally to handle secure sessions and to maintain internal state:

- "send", "receive" and "pending" function overrides - secure socket handling
- `httpd_set_sess_send_override()`
- `httpd_set_sess_recv_override()`
- `httpd_set_sess_pending_override()`
- `httpd_set_send_override()`
- `httpd_set_recv_override()`
- `httpd_set_pending_override()`
- "transport context" - both global and session
- `httpd_sess_get_transport_ctx()` - returns SSL used for the session
- `httpd_sess_set_transport_ctx()`
- `httpd_get_global_transport_ctx()` - returns the shared SSL context
- `httpd_config_t.global_transport_ctx`
- `httpd_config_t.global_transport_ctx_free_fn`
- `httpd_config_t.open_fn` - used to set up secure sockets

Everything else can be used without limitations.

## Usage

Please see the example `protocols/https_server` to learn how to set up a secure server.

Basically all you need is to generate a certificate, embed it in the firmware, and provide
its pointers and lengths to the start function via the init struct.

The server can be started with or without SSL by changing a flag in the init struct.
This could be used e.g. for testing or in trusted environments where you prefer speed over security.

## Performance

The initial session setup can take about two seconds, or more with slower clock speeds or more
verbose logging. Subsequent requests through the open secure socket are much faster (down to under
100 ms).
2 changes: 2 additions & 0 deletions components/esp_https_server/component.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
COMPONENT_SRCDIRS := src
COMPONENT_ADD_INCLUDEDIRS := include
112 changes: 112 additions & 0 deletions components/esp_https_server/include/esp_https_server.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef _ESP_HTTPS_SERVER_H_
#define _ESP_HTTPS_SERVER_H_

#include <stdbool.h>
#include "esp_err.h"
#include "esp_http_server.h"

/**
* HTTPS server config struct
*
* Please use HTTPD_SSL_CONFIG_DEFAULT() to initialize it.
*/
struct httpd_ssl_config {
/**
* Underlying HTTPD server config
*
* Parameters like task stack size and priority can be adjusted here.
*/
httpd_config_t httpd;

/** CA certificate */
const uint8_t *cacert_pem;

/** CA certificate byte length */
size_t cacert_len;

/** Private key */
const uint8_t *prvtkey_pem;

/** Private key byte length */
size_t prvtkey_len;

/** Enable SSL (default true) */
bool secure_enable;

/** Port used when SSL is enabled (default 443) */
uint16_t port_secure;

/** Port used when SSL is disabled (default 80) */
uint16_t port_insecure;
};

typedef struct httpd_ssl_config httpd_ssl_config_t;

/**
* Default config struct init
*
* (http_server default config had to be copied for customization)
*
* Notes:
* - port is set when starting the server, according to 'secure_enable'
* - one socket uses ~ 40kB RAM with SSL, we reduce the default socket count to 4
* - SSL sockets are usually long-lived, closing LRU prevents pool exhaustion DOS
* - Stack size may need adjustments depending on the user application
*/
#define HTTPD_SSL_CONFIG_DEFAULT() { \
.httpd = { \
.task_priority = tskIDLE_PRIORITY+5, \
.stack_size = 10240, \
.server_port = 0, \
.ctrl_port = 32768, \
.max_open_sockets = 4, \
.max_uri_handlers = 8, \
.max_resp_headers = 8, \
.backlog_conn = 5, \
.lru_purge_enable = true, \
.recv_wait_timeout = 5, \
.send_wait_timeout = 5, \
.global_user_ctx = NULL, \
.global_user_ctx_free_fn = NULL, \
.global_transport_ctx = NULL, \
.global_transport_ctx_free_fn = NULL, \
.open_fn = NULL, \
.close_fn = NULL, \
}, \
.secure_enable = true, \
.port_secure = 443, \
.port_insecure = 80, \
}

/**
* Create a SSL capable HTTP server (secure mode may be disabled in config)
*
* @param[in,out] config - server config, must not be const. Does not have to stay valid after
* calling this function.
* @param[out] handle - storage for the server handle, must be a valid pointer
* @return success
*/
esp_err_t httpd_ssl_start(httpd_handle_t *handle, httpd_ssl_config_t *config);

/**
* Stop the server. Blocks until the server is shut down.
*
* @param[in] handle
*/
void httpd_ssl_stop(httpd_handle_t handle);

#endif // _ESP_HTTPS_SERVER_H_
220 changes: 220 additions & 0 deletions components/esp_https_server/src/https_server.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// Copyright 2018 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <string.h>
#include "esp_https_server.h"
#include "openssl/ssl.h"
#include "esp_log.h"
#include "sdkconfig.h"

const static char *TAG = "esp_https_server";

/**
* SSL socket close handler
*
* @param[in] ctx - session transport context (SSL context we stored there)
*/
static void httpd_ssl_close(void *ctx)
{
assert(ctx != NULL);
SSL_shutdown(ctx);
SSL_free(ctx);
ESP_LOGD(TAG, "Secure socket closed");
}

/**
* SSL socket pending-check function
*
* @param server
* @param sockfd
* @return number of pending bytes, negative on error
*/
static int httpd_ssl_pending(httpd_handle_t server, int sockfd)
{
SSL *ssl = httpd_sess_get_transport_ctx(server, sockfd);
assert(ssl != NULL);
return SSL_pending(ssl);
}

/**
* Receive from a SSL socket
*
* @param server
* @param sockfd
* @param buf
* @param buf_len
* @param flags
* @return bytes read, negative on error
*/
static int httpd_ssl_recv(httpd_handle_t server, int sockfd, char *buf, size_t buf_len, int flags)
{
SSL *ssl = httpd_sess_get_transport_ctx(server, sockfd);
assert(ssl != NULL);
return SSL_read(ssl, buf, buf_len);
}

/**
* Send to a SSL socket
*
* @param server
* @param sockfd
* @param buf
* @param buf_len
* @param flags
* @return bytes sent, negative on error
*/
static int httpd_ssl_send(httpd_handle_t server, int sockfd, const char *buf, size_t buf_len, int flags)
{
SSL *ssl = httpd_sess_get_transport_ctx(server, sockfd);
assert(ssl != NULL);
return SSL_write(ssl, buf, buf_len);
}

/**
* Open a SSL socket for the server.
* The fd is already open and ready to read / write raw data.
*
* @param server
* @param sockfd - raw socket fd
* @return success
*/
static esp_err_t httpd_ssl_open(httpd_handle_t server, int sockfd)
{
assert(server != NULL);

// Retrieve the SSL context from the global context field (set in config)
SSL_CTX *global_ctx = httpd_get_global_transport_ctx(server);
assert(global_ctx != NULL);

SSL *ssl = SSL_new(global_ctx);
if (NULL == ssl) {
ESP_LOGE(TAG, "SSL_new ret NULL (out of memory)");
return ESP_ERR_NO_MEM;
}

if (1 != SSL_set_fd(ssl, sockfd)) {
ESP_LOGE(TAG, "fail to set SSL fd");
goto teardown;
}

ESP_LOGD(TAG, "SSL accept");
if (1 != SSL_accept(ssl)) {
ESP_LOGW(TAG, "fail to SSL_accept - handshake error");
goto teardown;
}

// Store the SSL session into the context field of the HTTPD session object
httpd_sess_set_transport_ctx(server, sockfd, ssl, httpd_ssl_close);

// Set rx/tx/pending override functions
httpd_set_sess_send_override(server, sockfd, httpd_ssl_send);
httpd_set_sess_recv_override(server, sockfd, httpd_ssl_recv);
httpd_set_sess_pending_override(server, sockfd, httpd_ssl_pending);

// all access should now go through SSL

ESP_LOGD(TAG, "Secure socket open");

return ESP_OK;

teardown:
SSL_free(ssl);
return ESP_FAIL;
}

/**
* Tear down the HTTPD global transport context
*
* @param ctx
*/
static void free_secure_context(void *ctx)
{
assert(ctx != NULL);

ESP_LOGI(TAG, "Server shuts down, releasing SSL context");
SSL_CTX_free(ctx);
}

/**
* Create and perform basic init of a SSL_CTX, or return NULL on failure
*
* @return ctx or null
*/
static SSL_CTX *create_secure_context(const struct httpd_ssl_config *config)
{
SSL_CTX *ctx = NULL;

ESP_LOGD(TAG, "SSL server context create");
ctx = SSL_CTX_new(TLS_server_method());
if (NULL != ctx) {
//region SSL ctx alloc'd
ESP_LOGD(TAG, "SSL ctx set own cert");
if (SSL_CTX_use_certificate_ASN1(ctx, config->cacert_len, config->cacert_pem)
&& SSL_CTX_use_PrivateKey_ASN1(0, ctx, config->prvtkey_pem, (long) config->prvtkey_len)) {
return ctx;
}
else {
ESP_LOGE(TAG, "Failed to set certificate");
SSL_CTX_free(ctx);
ctx = NULL;
}
} else {
ESP_LOGE(TAG, "Failed to create SSL context");
}
return NULL;
}

/** Start the server */
esp_err_t httpd_ssl_start(httpd_handle_t *pHandle, struct httpd_ssl_config *config)
{
assert(config != NULL);
assert(pHandle != NULL);

ESP_LOGI(TAG, "Starting server");

if (config->secure_enable) {
SSL_CTX *ctx = create_secure_context(config);
if (!ctx) {
return ESP_FAIL;
}

ESP_LOGD(TAG, "SSL context ready");

// set SSL specific config
config->httpd.global_transport_ctx = ctx;
config->httpd.global_transport_ctx_free_fn = free_secure_context;
config->httpd.open_fn = httpd_ssl_open; // the open function configures the created SSL sessions

config->httpd.server_port = config->port_secure;
} else {
ESP_LOGD(TAG, "SSL disabled, using plain HTTP");
config->httpd.server_port = config->port_insecure;
}

httpd_handle_t handle = NULL;

esp_err_t ret = httpd_start(&handle, &config->httpd);
if (ret != ESP_OK) return ret;

*pHandle = handle;

ESP_LOGI(TAG, "Server listening on port %d", config->httpd.server_port);
return ESP_OK;
}

/** Stop the server */
void httpd_ssl_stop(httpd_handle_t handle)
{
httpd_stop(handle);
}
6 changes: 6 additions & 0 deletions examples/protocols/https_server/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(https_server)
Loading

0 comments on commit 4947ff4

Please sign in to comment.