当前位置: 首页 > article >正文

【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 启动服务器

esp_err_t httpd_ssl_start(httpd_handle_t *handle, httpd_ssl_config_t *config)
  • handle:HTTPS句柄;
  • config:服务器配置。
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 代码

#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证书导致,客户端或服务器认为对方不安全。 


http://www.kler.cn/a/549471.html

相关文章:

  • 【动态规划】落花人独立,微雨燕双飞 - 8. 01背包问题
  • 【Java学习】二维数组
  • 蓝桥杯---颜色分类(leetcode第75题)题解
  • Ollama命令使用指南
  • 论文阅读_用于低频隔振的高负刚度新型阵列磁性弹簧的分析与设计_2
  • 结构型模式---代理模式
  • EasyRTC视频通话WebP2P技术:轻量化SDK助力嵌入式设备实时音视频通信
  • Vue.js 组件开发深入解析:Vue 2 vs Vue 3
  • 【漫话机器学习系列】094.交叉熵(Cross-Entropy)
  • 数据结构------单向链表。
  • 苍穹外卖day4 redis相关简单知识 店铺营业状态设置
  • Linux 基础IO——重定向和缓冲区
  • 大疆无人机需要的kml文件如何制作kml导出(大疆KML文件)
  • Instagram与小红书的自动化运营
  • Vite入门指南
  • github用户名密码登陆失效了
  • Mac上搭建宝塔环境并部署PHP项目
  • Ubuntu 连接 air pods
  • ios中常见的设计原则和设计模式
  • ARINC 429详解