【ESP32】ESP-IDF开发 | WiFi开发 | HTTPS服务器 + 搭建例程

1. 简介

1.1 HTTPS

HTTPS(HyperText Transfer Protocol over Secure Socket Layer),全称安全套接字层超文本传输协议,一般理解为HTTP+SSL/TLS ,通过SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。该协议使用443端口进行通信。

它解决的是HTTP协议明文传输的不安全性,HTTPS协议在数据进行传输之前,对数据进行加密,然后再发送到服务器。这样,就算数据被第三者所截获,但是由于数据是加密的,所以你的个人信息仍然是安全的。

1.2 SSL/TLS

SSL(Secure Socket Layer,安全套接字层):1994年为 Netscape 所研发,SSL 协议位于 TCP/IP 协议与各种应用层协议之间,为数据通讯提供安全支持。

TLS(Transport Layer Security,传输层安全):其前身是 SSL,它最初的几个版本(SSL 1.0、SSL 2.0、SSL 3.0)由网景公司开发,1999年从 3.1 开始被 IETF 标准化并改名,发展至今已经有 TLS 1.0、TLS 1.1、TLS 1.2 三个版本。SSL 3.0和TLS 1.0由于存在安全漏洞,已经很少被使用到。

1.3 通信流程

2. 例程

因为HTTPS相对于HTTP只是多了加密的过程,所以例程大体是沿用前一篇文章的代码,所以下面只会讲解加密部分的代码。

2.1 函数API

2.1.1 启动服务器

cpp 复制代码
esp_err_t httpd_ssl_start(httpd_handle_t *handle, httpd_ssl_config_t *config)
  • handle:HTTPS句柄;
  • config:服务器配置。
cpp 复制代码
struct httpd_ssl_config {
    httpd_config_t httpd;
    const uint8_t *servercert;
    size_t servercert_len;
    const uint8_t *cacert_pem;
    size_t cacert_len;
    const uint8_t *prvtkey_pem;
    size_t prvtkey_len;
    bool use_ecdsa_peripheral;
    uint8_t ecdsa_key_efuse_blk;
    httpd_ssl_transport_mode_t transport_mode;
    uint16_t port_secure;
    uint16_t port_insecure;
    bool session_tickets;
    bool use_secure_element;
    esp_https_server_user_cb *user_cb;
    void *ssl_userdata;
    esp_tls_handshake_callback cert_select_cb;
    const char** alpn_protos;
};

配置结构体的内容比较多,ESP-IDF也提供了HTTPD_SSL_CONFIG_DEFAULT宏进行快速默认配置,所以下面只解释一些常用的配置:

  • servercert:服务器证书;
  • servercert_len:服务器证书长度;
  • cacert_pem:CA证书;
  • cacert_len:CA证书长度;
  • prvtkey_pem:私钥;
  • prvtkey_len:私钥长度;
  • port_secure:安全端口,即采用https访问时的端口,默认为443;
  • port_insecure:非安全端口,即采用http访问时的端口,默认为80;
  • user_cb:用户回调,主要用于客户端连接或断开时进行自定义处理;

2.2 代码

cpp 复制代码
#include "freertos/FreeRTOS.h"

#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "sys/socket.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "netdb.h"
#include "arpa/inet.h"
#include "esp_https_server.h"
#include "esp_tls.h"
#include "mbedtls/base64.h"

#include <string.h>

#define TAG "app"

static httpd_handle_t server_handle;

static const uint8_t servercert[] = 
"-----BEGIN CERTIFICATE-----\n\
MIIDKzCCAhOgAwIBAgIUBxM3WJf2bP12kAfqhmhhjZWv0ukwDQYJKoZIhvcNAQEL\n\
BQAwJTEjMCEGA1UEAwwaRVNQMzIgSFRUUFMgc2VydmVyIGV4YW1wbGUwHhcNMTgx\n\
MDE3MTEzMjU3WhcNMjgxMDE0MTEzMjU3WjAlMSMwIQYDVQQDDBpFU1AzMiBIVFRQ\n\
UyBzZXJ2ZXIgZXhhbXBsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n\
ALBint6nP77RCQcmKgwPtTsGK0uClxg+LwKJ3WXuye3oqnnjqJCwMEneXzGdG09T\n\
sA0SyNPwrEgebLCH80an3gWU4pHDdqGHfJQa2jBL290e/5L5MB+6PTs2NKcojK/k\n\
qcZkn58MWXhDW1NpAnJtjVniK2Ksvr/YIYSbyD+JiEs0MGxEx+kOl9d7hRHJaIzd\n\
GF/vO2pl295v1qXekAlkgNMtYIVAjUy9CMpqaQBCQRL+BmPSJRkXBsYk8GPnieS4\n\
sUsp53DsNvCCtWDT6fd9D1v+BB6nDk/FCPKhtjYOwOAZlX4wWNSZpRNr5dfrxKsb\n\
jAn4PCuR2akdF4G8WLUeDWECAwEAAaNTMFEwHQYDVR0OBBYEFMnmdJKOEepXrHI/\n\
ivM6mVqJgAX8MB8GA1UdIwQYMBaAFMnmdJKOEepXrHI/ivM6mVqJgAX8MA8GA1Ud\n\
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBADiXIGEkSsN0SLSfCF1VNWO3\n\
emBurfOcDq4EGEaxRKAU0814VEmU87btIDx80+z5Dbf+GGHCPrY7odIkxGNn0DJY\n\
W1WcF+DOcbiWoUN6DTkAML0SMnp8aGj9ffx3x+qoggT+vGdWVVA4pgwqZT7Ybntx\n\
bkzcNFW0sqmCv4IN1t4w6L0A87ZwsNwVpre/j6uyBw7s8YoJHDLRFT6g7qgn0tcN\n\
ZufhNISvgWCVJQy/SZjNBHSpnIdCUSJAeTY2mkM4sGxY0Widk8LnjydxZUSxC3Nl\n\
hb6pnMh3jRq4h0+5CZielA4/a+TdrNPv/qok67ot/XJdY3qHCCd8O2b14OVq9jo=\n\
-----END CERTIFICATE-----";

static const uint8_t prvkey[] = 
"-----BEGIN PRIVATE KEY-----\n\
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwYp7epz++0QkH\n\
JioMD7U7BitLgpcYPi8Cid1l7snt6Kp546iQsDBJ3l8xnRtPU7ANEsjT8KxIHmyw\n\
h/NGp94FlOKRw3ahh3yUGtowS9vdHv+S+TAfuj07NjSnKIyv5KnGZJ+fDFl4Q1tT\n\
aQJybY1Z4itirL6/2CGEm8g/iYhLNDBsRMfpDpfXe4URyWiM3Rhf7ztqZdveb9al\n\
3pAJZIDTLWCFQI1MvQjKamkAQkES/gZj0iUZFwbGJPBj54nkuLFLKedw7DbwgrVg\n\
0+n3fQ9b/gQepw5PxQjyobY2DsDgGZV+MFjUmaUTa+XX68SrG4wJ+DwrkdmpHReB\n\
vFi1Hg1hAgMBAAECggEAaTCnZkl/7qBjLexIryC/CBBJyaJ70W1kQ7NMYfniWwui\n\
f0aRxJgOdD81rjTvkINsPp+xPRQO6oOadjzdjImYEuQTqrJTEUnntbu924eh+2D9\n\
Mf2CAanj0mglRnscS9mmljZ0KzoGMX6Z/EhnuS40WiJTlWlH6MlQU/FDnwC6U34y\n\
JKy6/jGryfsx+kGU/NRvKSru6JYJWt5v7sOrymHWD62IT59h3blOiP8GMtYKeQlX\n\
49om9Mo1VTIFASY3lrxmexbY+6FG8YO+tfIe0tTAiGrkb9Pz6tYbaj9FjEWOv4Vc\n\
+3VMBUVdGJjgqvE8fx+/+mHo4Rg69BUPfPSrpEg7sQKBgQDlL85G04VZgrNZgOx6\n\
pTlCCl/NkfNb1OYa0BELqWINoWaWQHnm6lX8YjrUjwRpBF5s7mFhguFjUjp/NW6D\n\
0EEg5BmO0ePJ3dLKSeOA7gMo7y7kAcD/YGToqAaGljkBI+IAWK5Su5yldrECTQKG\n\
YnMKyQ1MWUfCYEwHtPvFvE5aPwKBgQDFBWXekpxHIvt/B41Cl/TftAzE7/f58JjV\n\
MFo/JCh9TDcH6N5TMTRS1/iQrv5M6kJSSrHnq8pqDXOwfHLwxetpk9tr937VRzoL\n\
CuG1Ar7c1AO6ujNnAEmUVC2DppL/ck5mRPWK/kgLwZSaNcZf8sydRgphsW1ogJin\n\
7g0nGbFwXwKBgQCPoZY07Pr1TeP4g8OwWTu5F6dSvdU2CAbtZthH5q98u1n/cAj1\n\
noak1Srpa3foGMTUn9CHu+5kwHPIpUPNeAZZBpq91uxa5pnkDMp3UrLIRJ2uZyr8\n\
4PxcknEEh8DR5hsM/IbDcrCJQglM19ZtQeW3LKkY4BsIxjDf45ymH407IQKBgE/g\n\
Ul6cPfOxQRlNLH4VMVgInSyyxWx1mODFy7DRrgCuh5kTVh+QUVBM8x9lcwAn8V9/\n\
nQT55wR8E603pznqY/jX0xvAqZE6YVPcw4kpZcwNwL1RhEl8GliikBlRzUL3SsW3\n\
q30AfqEViHPE3XpE66PPo6Hb1ymJCVr77iUuC3wtAoGBAIBrOGunv1qZMfqmwAY2\n\
lxlzRgxgSiaev0lTNxDzZkmU/u3dgdTwJ5DDANqPwJc6b8SGYTp9rQ0mbgVHnhIB\n\
jcJQBQkTfq6Z0H6OoTVi7dPs3ibQJFrtkoyvYAbyk36quBmNRjVh6rc8468bhXYr\n\
v/t+MeGJP/0Zw8v/X2CFll96\n\
-----END PRIVATE KEY-----";

static const char index_html[] = " \
<!DOCTYPE html> \
<html> \
<head> \
<meta charset=\"utf-8\"> \
<title>index</title> \
</head> \
\
<body> \
    <h1>Hello from ESP32</h1> \
</body> \
    <form action=\"/hello\" method=\"post\"> \
        <label for=\"name\">What's your name:</label> \
        <input type=\"text\" id=\"name\" name=\"name\" required> \
        <input type=\"submit\" value=\"OK\"></button> \
    </form> \
</html> \
";

static const char hello_html_template[] = " \
<!DOCTYPE html> \
<html> \
<head> \
<meta charset=\"utf-8\"> \
<title>hello</title> \
</head> \
\
<body> \
    <h1>Oh, Hello %s</h1> \
</body> \
\
</html> \
";


static esp_err_t index_get_handler(httpd_req_t *req)
{
    /* 获取Host信息 */
    size_t buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
    if (buf_len > 1) {
        char *buf = malloc(buf_len);
        if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) {
            ESP_LOGI(TAG, "Get request to host: %s", buf);
        }
        free(buf);
    }

    /* 回复数据包 */
    httpd_resp_sendstr(req, index_html);

    return ESP_OK;
}

static const httpd_uri_t index_uri = {
    .uri       = "/index",
    .method    = HTTP_GET,
    .handler   = index_get_handler,
    .user_ctx  = NULL
};

static esp_err_t hello_post_handler(httpd_req_t *req)
{
    /* 获取Host信息 */
    size_t buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
    if (buf_len > 1) {
        char *buf = malloc(buf_len);
        if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) {
            ESP_LOGI(TAG, "Post request to host: %s", buf);
        }
        free(buf);
    }

    /* 获取内容长度 */
    int len = 0;
    {
        char *buf = malloc(128);
        memset(buf, 0, 128);
        if (httpd_req_get_hdr_value_str(req, "Content-Length", buf, 128) != ESP_OK) {
            ESP_LOGE(TAG, "Get content length failed");
            return ESP_FAIL;
        }
        len = atoi(buf) + 1;
        free(buf);
    }

    /* 获取表单数据 */
    char *buf = malloc(len);
    memset(buf, 0, len);
    if (httpd_req_recv(req, buf, len) <= 0) {
        ESP_LOGE(TAG, "Receive request content failed");
        return ESP_FAIL;
    }
    if (strstr(buf, "name=") == NULL) {
        ESP_LOGE(TAG, "Can't found fleid \"name\"");
        free(buf);
        return ESP_FAIL;
    }

    /* 发送数据 */
    char *hello_html = malloc(1024);
    snprintf(hello_html, 1024, hello_html_template, buf + strlen("name="));
    httpd_resp_sendstr(req, hello_html);
    free(buf);
    free(hello_html);

    return ESP_OK;
}

static const httpd_uri_t hello_uri = {
    .uri       = "/hello",
    .method    = HTTP_POST,
    .handler   = hello_post_handler,
    .user_ctx  = NULL
};

static void print_peer_cert_info(const mbedtls_ssl_context *ssl)
{
    const mbedtls_x509_crt *cert;
    const size_t buf_size = 1024;
    char *buf = calloc(buf_size, sizeof(char));
    if (buf == NULL) {
        ESP_LOGE(TAG, "Out of memory - Callback execution failed!");
        return;
    }

    cert = mbedtls_ssl_get_peer_cert(ssl);
    if (cert != NULL) {
        mbedtls_x509_crt_info((char *) buf, buf_size - 1, "    ", cert);
        ESP_LOGI(TAG, "Peer certificate info:\n%s", buf);
    } else {
        ESP_LOGW(TAG, "Could not obtain the peer certificate!");
    }

    free(buf);
}

static void https_server_user_callback(esp_https_server_user_cb_arg_t *user_cb)
{
    mbedtls_ssl_context *ssl_ctx = NULL;

    switch(user_cb->user_cb_state) {
        case HTTPD_SSL_USER_CB_SESS_CREATE:
            int sockfd = -1;
            esp_err_t esp_ret;
            esp_ret = esp_tls_get_conn_sockfd(user_cb->tls, &sockfd);
            if (esp_ret != ESP_OK) {
                ESP_LOGE(TAG, "Error in obtaining the sockfd from tls context");
                break;
            }
            ESP_LOGI(TAG, "Socket FD: %d", sockfd);

            ssl_ctx = (mbedtls_ssl_context *) esp_tls_get_ssl_context(user_cb->tls);
            if (ssl_ctx == NULL) {
                ESP_LOGE(TAG, "Error in obtaining ssl context");
                break;
            }
            
            ESP_LOGI(TAG, "Current Ciphersuite: %s", mbedtls_ssl_get_ciphersuite(ssl_ctx));
            break;

        case HTTPD_SSL_USER_CB_SESS_CLOSE:
            ssl_ctx = (mbedtls_ssl_context *) esp_tls_get_ssl_context(user_cb->tls);
            if (ssl_ctx == NULL) {
                ESP_LOGE(TAG, "Error in obtaining ssl context");
                break;
            }
            print_peer_cert_info(ssl_ctx);
            break;

        default:
            break;
    }
}

static void event_handler(void* arg,
                               esp_event_base_t event_base,
                               int32_t event_id,
                               void* event_data)
{
    if (event_base == IP_EVENT) {
        if (event_id == IP_EVENT_STA_GOT_IP) {
            /* 配置 */
            httpd_ssl_config_t conf = HTTPD_SSL_CONFIG_DEFAULT();

            conf.servercert = servercert;
            conf.servercert_len = sizeof(servercert);
            conf.prvtkey_pem = prvkey;
            conf.prvtkey_len = sizeof(prvkey);
            conf.user_cb = https_server_user_callback;

            /* 启动服务器 */
            esp_err_t ret = httpd_ssl_start(&server_handle, &conf);
            if (ESP_OK != ret) {
                ESP_LOGI(TAG, "Error starting server!");
                return;
            }

            /* 注册URI */
            httpd_register_uri_handler(server_handle, &index_uri);
            httpd_register_uri_handler(server_handle, &hello_uri);

            ip_event_got_ip_t *data = event_data;
            ESP_LOGI(TAG, "HTTPS server started at https://" IPSTR "/index", IP2STR(&(data->ip_info.ip)));
        }
    } else if (event_base == WIFI_EVENT) {
        if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
            static uint32_t retry = 1;
            ESP_LOGW(TAG, "Try reconnect WiFi, retry %lu", retry);
            vTaskDelay(500 / portTICK_PERIOD_MS);
            esp_wifi_connect();
            retry++;
        } else if (event_id == WIFI_EVENT_STA_START) {
            esp_wifi_connect();
        }
    }
}

int app_main()
{
    /* 初始化NVS */
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ESP_ERROR_CHECK(nvs_flash_init());
    }

    /* 初始化WiFi协议栈 */
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        NULL,
                                                        NULL));

    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        NULL,
                                                        NULL));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "Your SSID",
            .password = "Your Password",
            .threshold.authmode = WIFI_AUTH_WPA_WPA2_PSK,
        },
    };

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    return 0;
}

https服务器需要配置自己的公钥和私钥,我这里使用的是官方例程给的,当然也可以自己生成;例程中也注册了用户回调,来演示如何获取客户端的套接字描述符和查看客户端的SSL验证信息。

调用httpd_ssl_start函数即可启动服务器,服务器启动后调用httpd_register_uri_handler函数注册URI服务,这里后面就跟上一篇文章的一样了。

网页的内容就跟之前的一样,在输入框中填写自己的名字,点击"OK"按钮就会跳转到另一个页面。

例程的log有时候会弹出0x7780错误,一般是由于没有设置CA证书导致,客户端或服务器认为对方不安全。

相关推荐
googleccsdn11 小时前
ESNP LAB 笔记:配置MPLS(Part4)
网络·笔记·网络协议
tan180°11 小时前
Boost搜索引擎 网络库与前端(4)
linux·网络·c++·搜索引擎
小小菜鸡ing11 小时前
pymysql
java·服务器·数据库
Dontla12 小时前
Docker多共享网络配置策略(Docker多网络、Docker networks、Docker Compose网络、Docker网络、Docker共享网络)
网络·docker·容器
LS·Cui12 小时前
单片机按键示例功能
单片机
【ql君】qlexcel12 小时前
MCU上电到运行的全过程
单片机·嵌入式硬件·mcu·启动过程
LUCIAZZZ12 小时前
HTTPS优化简单总结
网络·网络协议·计算机网络·http·https·操作系统
搞一搞汽车电子12 小时前
S32K3平台eMIOS 应用说明
开发语言·驱动开发·笔记·单片机·嵌入式硬件·汽车
l1t13 小时前
轻量级XML读写库Mini-XML的编译和使用
xml·c语言·解析器
wanhengidc13 小时前
云手机运行流畅,秒开不卡顿
运维·网络·科技·游戏·智能手机