ESP32 SSL/TLS 双向认证实践

SSL/TLS 双向认证指的是相互校验,服务器需要校验每个client,client也需要校验服务器。此篇文章使用两个 ESP32 分别做 HTTPS server 和 HTTPS client 来尝试了解 ESP32 HTTPS 双向认证的实现流程。分为以下四部分:

  • 客户端以及服务器端的证书生成
  • 服务器端代码编写
  • 客户端代码编写
  • 测试验证
  • 附录

1 客户端以及服务器端的证书生成

在双向认证前,需要生成客户端和服务器端的以下证书:

  • server 需要 server.key 、server.crt 、ca.crt(client 端 CA 证书)
  • client 需要 client.key 、client.crt 、ca.crt(server 端 CA 证书)
    上述证书可以通过 openssl 指令生成,在此也可以使用总结好的脚本 gen_crt.sh,内容如下:
#!/bin/bash

#
# Generate the certificates and keys for testing.
#

SAVEIFS=$IFS
IFS=$(echo -en "\n\b")

ROOT_SUBJECT="/C=C1/ST=JS1/L=WX1/O=ESP1/OU=ESP1/CN=Server1 CA/emailAddress=ESP1"
LEVEL2_SUBJECT="/C=C2/ST=JS22/L=WX22/O=ESP22/OU=ESP22/CN=Server22 CA/emailAddress=ESP22"
LEVEL3_SUBJECT="/C=C3/ST=JS333/L=WX333/O=ESP333/OU=ESP333/CN=Server333 CA/emailAddress=ESP333"

# private key generation
openssl genrsa -out ca.key 2048
openssl genrsa -out server.key 2048
openssl genrsa -out client.key 2048

# cert requests
openssl req -new -key ca.key -out ca.csr -text -subj $ROOT_SUBJECT
openssl req -new -key server.key -out server.csr -text -subj $LEVEL2_SUBJECT
openssl req -new -key client.key -out client.csr -text -subj $LEVEL3_SUBJECT

# generate the actual certs.
openssl x509 -req -in ca.csr -out ca.pem -sha256 -days 5000 -signkey ca.key -text -extensions v3_ca
openssl x509 -req -in server.csr -out server.pem -sha256 -CAcreateserial -days 5000 -CA ca.pem -CAkey ca.key -text -extensions v3_ca
openssl x509 -req -in client.csr -out client.pem -sha256 -CAcreateserial -days 5000 -CA ca.pem -CAkey ca.key -text -extensions v3_ca

rm *.csr
rm *.srl

mv ca.* ./main
mv server.* ./main
mv client.* ./main

具体的使用方式为:将上述脚本 gen_crt.sh 放入您需要生成 ESP HTTPS 客户端或服务器证书的 ESP-IDF 工程下,打开工程对应的终端,输入

. ./gen_crt.sh

就可以看到以下 log 信息:

[11:54:03] [~/github/esp-idf/rel4.4/esp-idf/examples/protocols/esp_http_client] git(1329b19fe4) 🔥 ❱❱❱ . ./gen_crt.sh
Generating RSA private key, 2048 bit long modulus (2 primes)
...........................................................+++++
.................+++++
e is 65537 (0x010001)
Generating RSA private key, 2048 bit long modulus (2 primes)
....+++++
...........................................................................................+++++
e is 65537 (0x010001)
Generating RSA private key, 2048 bit long modulus (2 primes)
.............................................+++++
......+++++
e is 65537 (0x010001)
Can't load /home/zhengzhong/.rnd into RNG
140139168797120:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/zhengzhong/.rnd
Can't load /home/zhengzhong/.rnd into RNG
140396157870528:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/zhengzhong/.rnd
Can't load /home/zhengzhong/.rnd into RNG
140703026512320:error:2406F079:random number generator:RAND_load_file:Cannot open file:../crypto/rand/randfile.c:88:Filename=/home/zhengzhong/.rnd
Signature ok
subject=C = C1, ST = JS1, L = WX1, O = ESP1, OU = ESP1, CN = Server1 CA, emailAddress = ESP1
Getting Private key
Signature ok
subject=C = C2, ST = JS22, L = WX22, O = ESP22, OU = ESP22, CN = Server22 CA, emailAddress = ESP22
Getting CA Private Key
Signature ok
subject=C = C3, ST = JS333, L = WX333, O = ESP333, OU = ESP333, CN = Server333 CA, emailAddress = ESP333
Getting CA Private Key

脚本运行完毕后,即可看到 ESP-IDF 对应工程下的 main 文件夹里新增了一些证书,如下:
请添加图片描述

到此,证书生成的步骤已经完成。此时:

  • 客户端需要的证书为 ca.pem, client.pem, client.key
  • 服务器端需要的证书为 ca.pem, server.pem, server.key

2 服务器端代码编写

这部分以 ESP-IDF v4.4.1 里的 https_server/simple example 为例,首先需要确保第一步生成的对应 ca.pem, server.pem, client.key 证书放在了 main 文件夹下:
请添加图片描述
然后需要让代码成功加载这些证书并使用,首先修改项目 main 文件夹下 CMakeLists.txt 为如下:

idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS "."
                    EMBED_TXTFILES "ca.pem"
                                   "server.pem"
                                   "server.key")

然后替换掉 main.c 里原有的证书,修改后如下:

   extern const unsigned char cacert_pem_start[] asm("_binary_server_pem_start");
    extern const unsigned char cacert_pem_end[]   asm("_binary_server_pem_end");
    conf.cacert_pem = cacert_pem_start;
    conf.cacert_len = cacert_pem_end - cacert_pem_start;

    extern const unsigned char prvtkey_pem_start[] asm("_binary_server_key_start");
    extern const unsigned char prvtkey_pem_end[]   asm("_binary_server_key_end");
    conf.prvtkey_pem = prvtkey_pem_start;
    conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start;

    extern const unsigned char client_pem_start[] asm("_binary_ca_pem_start");
    extern const unsigned char client_pem_end[]   asm("_binary_ca_pem_end");
    conf.client_verify_cert_pem = client_pem_start;
    conf.client_verify_cert_len = client_pem_end - client_pem_start;

具体修改的代码地点请参考下图。
请添加图片描述
然后在 menuconfig 里配置 Wi-Fi SSID 和 Wi-Fi Password 后,烧录程序进 ESP32 即可。不过此时需要从 HTTPS server 运行的 log 里找到 server 的 IP 地址,方便 HTTPS client 后续进行连接。

在这次实践中,HTTPS server 的 IP 为 192.168.1.109。如下:
请添加图片描述
到这里,HTTPS server 端的代码编写修改工作全部结束。

3 客户端代码编写

这部分以 ESP-IDF v4.4.1 里的 esp_http_client example 为例,首先需要确保第一步生成的对应 ca.pem, client.pem, client.key 证书放在了 main 文件夹下:
请添加图片描述
然后需要让代码成功加载这些证书并使用,首先修改项目 main 文件夹下 CMakeLists.txt 为如下:

# Embed the server root certificate into the final binary
#
# (If this was a component, we would set COMPONENT_EMBED_TXTFILES here.)
idf_component_register(SRCS "esp_http_client_example.c"
                    INCLUDE_DIRS "."
                    EMBED_TXTFILES howsmyssl_com_root_cert.pem
                    postman_root_cert.pem
                    ca.pem
                    client.pem
                    client.key)

然后在 main.c 里新增对应的证书,如下:

extern const char server_root_cert_pem_start[] asm("_binary_ca_pem_start");
extern const char server_root_cert_pem_end[]   asm("_binary_ca_pem_end");

extern const char client_cacert_pem_start[] asm("_binary_client_pem_start");
extern const char client_cacert_pem_pem_end[]   asm("_binary_client_pem_end");

extern const char client_prvtkey_pem_start[] asm("_binary_client_key_start");
extern const char client_prvtkey_pem_end[]   asm("_binary_client_key_end");

具体修改的代码地点请参考下图。
请添加图片描述
然后需要在 esp_http_client_config_t 结构体里配置使用上述证书,修改如下:

    esp_http_client_config_t config = {
        .url = "https://192.168.1.109:443",
        .event_handler = _http_event_handler,
//        .crt_bundle_attach = esp_crt_bundle_attach,
        .cert_pem = server_root_cert_pem_start,
        .client_cert_pem = client_cacert_pem_start,
        .client_key_pem = client_prvtkey_pem_start,
        .skip_cert_common_name_check = true,
    };

具体修改的代码地点请参考下图。
请添加图片描述
同时也修改了上图里的 log 显示等级为 warning。方便后续观测测试结果。

注:上述 esp_http_client_config_t 结构体里的 url 成员变量里的 IP 应为实际观测到的 HTTPS server IP,在这里使用的是第二步观测到的 192.168.1.109。

最后在 menuconfig 里配置 Wi-Fi SSID 和 Wi-Fi Password 后,烧录程序进 ESP32 即可。到这里,HTTPS client 端的代码编写修改工作全部结束。

4 测试验证

同时烧录上述两个工程到两个 ESP32 并观测 log,可以看到 HTTPS 链接成功建立,如下:
请添加图片描述

5 附录

5.1 相关参考

5.2 Q & A

  1. 证书生成脚本里的 ROOT_SUBJECT 是什么?CN 检查 为什么要关掉
    [ESP]ROOT_SUBJECT 里为用于生成 CA 证书的一些信息, CN 检查关闭的原因是客户端此时是按照 IP 访问的服务器端,如果设置 CN 检查, CN 字段为 IP 值,若服务器证书 CN 生成的时候设置成这个 IP,应该就可以访问了。总结就是 CN 字段客户端需和服务器证书的 CN 字段匹配。

  2. 这种情况下的 server 端的 CA 证书为啥和 client 是共用的?
    [ESP]这个 CA 同时签发了客户端和服务器的证书,就可以用这个 CA 去校验服务器或者客户端证书。

  3. 为什么需要屏蔽掉 crt_bundle_attach = esp_crt_bundle_attach?
    [ESP]crt_bundle_attach 里保存的校验服务器的 CA 证书都是受信任的 CA 证书,而自行使用 openssl 生成的 CA 证书是不受信任的,自然签发的证书也都是不受信任的,用受信任的 CA 证书链去校验不受信任的服务器证书肯定会校验失败。

  4. skip_cert_common_name_check = true 是因为此时 esp32 server 没有类似于域名的 common name 吗?
    [ESP]是的,对于自签证书,可以尝试将 server 端证书 CN 设置成 server 的 IP 地址,然后客户端的 URL 里面也是直接传入 IP 地址结构的 URL,此时打开 CN 检查即可校验通过。

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值