ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenSSL 实战:用 sslecho/echecho 示例从零构建 TLS 回显客户端与服务端(含 ECH 加密 ClientHello)

OpenSSL 实战:用 sslecho/echecho 示例从零构建 TLS 回显客户端与服务端(含 ECH 加密 ClientHello) OpenSSL 实战用 sslecho/echecho 示例从零构建 TLS 回显客户端与服务端含 ECH 加密 ClientHello【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl导读本文以 OpenSSL 仓库中的官方示例 demos/sslecho 为主线完整讲解如何用 OpenSSL 的 C API 编写一个最简单的 TLS 回显echo客户端与服务端从裸 TCP 连接、升级为 SSL 连接、到SSL_write/SSL_read收发数据全程不依赖任何第三方封装库。在此基础上文章进一步剖析该示例提供的 Encrypted Client HelloECH变体echecho展示启用 ECH 所需的最小代码改动。读完本文你将掌握 TLS 客户端/服务端的标准编程范式TCP connect → SSL_connect → SSL_read/write、自签名证书的生成与配置以及 OpenSSL 3.x 新增 ECH APIOSSL_ECHSTORE_*、SSL_CTX_set1_echstore、SSL_ech_get1_status的用法和状态码语义。示例概览一个控制台程序两种运行模式sslecho是一个控制台应用程序通过命令行参数决定运行模式sslecho s以服务端模式运行监听 TCP 端口4433源码中static const int server_port 4433;见 main.c等待客户端接入并回显收到的每一行文本sslecho c hostname以客户端模式运行连接指定主机名的服务端把键盘输入逐行发给服务端并把回显结果显示在终端。不带任何参数直接运行程序会打印用法说明usage()函数见 main.cUsage: sslecho s --or-- sslecho c hostname cclient, sserver, hostnamehostname of server该示例的服务端代码改编自 OpenSSL Wiki 上的 Simple TLS Server 教程在原有基础上增加了回显逻辑客户端代码则是全新编写用于与服务端建立连接并把键盘输入发送过去。程序启动流程主函数main.c的启动逻辑清晰打印启动横幅sslecho : Simple Echo Client/Server检查参数数量与首字母确定是客户端还是服务端客户端还需第二个参数服务端主机名调用create_context()创建SSL_CTX服务端用TLS_server_method()客户端用TLS_client_method()再由SSL_CTX_new()实例化见 main.c按模式分别执行服务端或客户端的连接与回显循环。在非 Windows 平台程序启动时还会执行signal(SIGPIPE, SIG_IGN)见 main.c忽略SIGPIPE信号这样当对端异常断开导致管道破裂时服务端进程不会因信号被终止而是由SSL_read/SSL_write返回错误码来处理。构建与运行Makefile 与共享库路径示例自带 Makefile同时构建sslecho和echecho两个二进制TESTS sslecho echecho CFLAGS -I../../include -g -Wall LDFLAGS -L../.. LDLIBS -lssl -lcrypto all: $(TESTS)关键点头文件搜索路径-I../../include指向仓库根目录下的 include即 OpenSSL 的头文件目录链接库为-lssl -lcrypto对应libssl.so与libcrypto.so当以共享库方式链接默认情况时运行前必须确保libcrypto与libssl在动态库搜索路径上Makefile 注释中给出了示例命令LD_LIBRARY_PATH../.. ./sslecho本地联调运行先启动服务端$ LD_LIBRARY_PATH../.. ./sslecho s再另开一个终端启动客户端localhost可换成实际主机名或 IP$ LD_LIBRARY_PATH../.. ./sslecho c localhost服务端会依次打印Client TCP connection accepted与Client SSL connection accepted客户端会打印TCP connection to server successful与SSL connection to server successful。此后在客户端输入任意文本并回车服务端打印Received: ...并原样回显客户端随即打印回显内容。客户端输入kill会触发服务端内置的关机指令源码注释为Terminate...with extreme prejudice服务端退出客户端直接按回车或 Ctrl-DEOF则正常结束本次连接。TLS 编程范式的三个关键步骤README 用四点高度概括了客户端代码所展示的核心要点这正是 OpenSSL 常规 TLS 编程的骨架与 SSL 服务器的连接起始于一次标准的 TCPconnectTCP 连接建立后客户端通过SSL_connect()将连接升级为 SSLSSL 握手完成后通过SSL_write()与SSL_read()收发数据整体流程相当简单。第一步TCP 层建连socket/connectsocket()创建套接字时使用AF_INET SOCK_STREAM见 main.c。服务端额外执行setsockopt(SO_REUSEADDR)便于快速重启、bind()、listen()客户端则直接调用connect()连接server_port对应的地址。客户端在 TCP 层使用的地址转换函数是inet_pton(AF_INET, rem_server_name, addr.sin_addr.s_addr)见 main.c因此传入的主机名需要是点分十进制 IP如127.0.0.1——这是示例的一个简化真实程序中通常会先用getaddrinfo()做域名解析。第二步把 TCP 连接升级为 SSL关键 API 序列如下客户端侧见 main.cssl SSL_new(ssl_ctx); /* 基于 SSL_CTX 创建 SSL 对象 */ SSL_set_fd(ssl, (int)client_skt); /* 把已连接的 TCP 套接字绑定到 SSL 对象 */ SSL_set_tlsext_host_name(ssl, rem_server_name); /* 设置 SNI */ SSL_set1_dnsname(ssl, rem_server_name); /* 配置证书主机名校验 */ SSL_connect(ssl); /* 执行 TLS 握手成功返回 1 */服务端侧则对应为见 main.cssl SSL_new(ssl_ctx); SSL_set_fd(ssl, (int)client_skt); /* 绑定 accept() 返回的客户端套接字 */ SSL_accept(ssl); /* 等待客户端发起 TLS 握手 */SSL_set1_dnsname()与SSL_set1_ipaddr()的声明位于 include/openssl/ssl.h.in二者分别用于在证书校验时按 DNS 名称或 IP 地址核对对端身份防止中间人冒充。第三步用 SSL_read/SSL_write 收发数据连接建立后数据读写与普通 socket 读写体验一致服务端循环SSL_read()把收到的内容用SSL_write()原样写回若读到kill\n则终止见 main.c客户端用fgets()从stdin取一行SSL_write()发送再SSL_read()等待回显见 main.c。注意SSL_read()的返回值语义返回 0 表示对端已关闭连接返回负数表示出错此时需用ERR_print_errors_fp(stderr)打印错误栈定位问题——这是示例中反复出现的错误处理模式。收尾SSL_shutdown 与资源释放程序退出路径见 main.c依次调用SSL_shutdown()发送关闭通知、SSL_free()释放 SSL 对象、SSL_CTX_free()释放上下文以及closesocket()关闭套接字。服务端在回显循环结束后还会执行SSL_shutdown(ssl); SSL_free(ssl);为下一个客户端做好准备见 main.c。证书与密钥自签名证书的生成与加载示例随附的 cert.pem 与 key.pem 是自签名证书其 Common Name 为localhost。README 建议最好使用真实主机名来生成 pem 文件这样客户端按主机名校验证书时才不会因名称不匹配而失败。生成自己的证书来自 A-SSL-Docs.txt示例目录下的 A-SSL-Docs.txt 给出了生成 RSA-4096 自签名证书的命令有效期 10 年openssl req -newkey rsa:4096 -x509 -sha256 -days 3650 -nodes -out cert.pem -keyout key.pem交互提示中可以一路回车接受默认值唯一需要认真填写的是 Common Name应输入localhost或实际主机名。同一对密钥可以同时供通信双方使用无论它们是否在同一台机器上。服务端与客户端的证书加载方式对比服务端通过SSL_CTX_use_certificate_chain_file(ctx, cert.pem)加载证书链、SSL_CTX_use_PrivateKey_file(ctx, key.pem, SSL_FILETYPE_PEM)加载私钥见 main.c客户端通过SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL)开启对服务端证书的强制校验并用SSL_CTX_load_verify_locations(ctx, cert.pem, NULL)把自签名证书加入信任列表见 main.c。源码注释特别说明真实应用中客户端通常会直接调用SSL_CTX_set_default_verify_paths(ctx)使用系统默认证书信任库本例因为用的是自签名证书才必须显式信任它。ECH 变体echecho 如何用最小改动启用加密 ClientHelloEncrypted Client HelloECH是 TLS 领域保护隐私的重要扩展它把 ClientHello 中的敏感字段尤其是 SNI 服务器名称加密起来防止网络中间人窥探用户访问的目标站点。OpenSSL 在该仓库中提供了共享模式shared-mode的 ECH 实现相关头文件为 include/openssl/ech.h。echecho.c 与sslecho功能完全一致但演示了启用 ECH 所需的最小代码改动。echecho二进制与sslecho拥有相同的用户界面echecho s起服务端echecho c ip起客户端不同之处在于它基于硬编码的 ECH 配置数据启用 ECH源码中的echconfig是 base64 编码的 ECHConfigListechprivbuf是包含 ECH 私钥与 ECHCONFIG 的 PEM 块见 echecho.c真实的服务端应改为从文件加载这些配置真实的客户端则应通过 DNS 获取 ECHConfigList。ECH 的三步启用法README 明确指出使用 ECH 只需做两件事通过OSSL_ECHSTORE_read_*系列 API 把 ECH 数据加载到OSSL_ECHSTORE通过SSL_CTX_set1_echstore()把它挂到 SSL 上下文上。至于用SSL_ech_get1_status()查询并打印 ECH 状态属于可选步骤。示例中的configure_ech()函数见 echecho.c完整展示了这个过程static int configure_ech(SSL_CTX *ctx, int server, unsigned char *buf, size_t len) { OSSL_ECHSTORE *es NULL; BIO *es_in BIO_new_mem_buf(buf, (int)len); if (es_in NULL || (es OSSL_ECHSTORE_new(NULL, NULL)) NULL) goto err; if (server OSSL_ECHSTORE_read_pem(es, es_in, 1) ! 1) goto err; if (!server OSSL_ECHSTORE_read_echconfiglist(es, es_in) ! 1) goto err; if (SSL_CTX_set1_echstore(ctx, es) ! 1) goto err; BIO_free_all(es_in); return 1; err: OSSL_ECHSTORE_free(es); BIO_free_all(es_in); return 0; }这里的关键差异在于加载方式服务端调用OSSL_ECHSTORE_read_pem(es, es_in, 1)从 PEM 块中读取 ECH 私钥与配置第三个参数1对应OSSL_ECH_FOR_RETRY即该配置用于 ECH 重试见 ech.h客户端调用OSSL_ECHSTORE_read_echconfiglist(es, es_in)只读取公开的 ECHConfigList不含私钥。OSSL_ECHSTORE是一个对外不透明的类型用于管理 ECHConfigList、ECH 私钥及相关元数据完整的 API 列表与签名可参考 doc/man3/SSL_set1_echstore.pod其中包括OSSL_ECHSTORE_new_config()生成新配置与密钥、OSSL_ECHSTORE_write_pem()导出 PEM、OSSL_ECHSTORE_downselect()选择条目、SSL_ech_set1_server_names()设置内外层 SNI等。该文档还说明当前版本仅支持 ECH 的共享模式不支持 split-mode且 ECH 版本为 RFC 9849OSSL_ECH_RFC9849_VERSION见 ech.h。运行 echecho按 README 给出的命令启动服务端与客户端$ LD_LIBRARY_PATH../.. ./echecho s $ LD_LIBRARY_PATH../.. ./echecho c localhost一切顺利时双方在每次连接建立后都会打印 ECH 状态ECH worked (status: 1, inner: localhost, outer: example.com)含义ECH 加密成功内层真实SNI 是localhost外层公开SNI 是配置中的公共名称example.com。看懂 ECH 状态码SSL_ech_get1_status()返回的状态码定义在 ech.h状态码宏定义含义4SSL_ECH_STATUS_BACKEND后端感知到ech_is_inner标记3SSL_ECH_STATUS_GREASE_ECH发送了 GREASE 且收到了 ECH 响应2SSL_ECH_STATUS_GREASE发生了 ECH GREASE1SSL_ECH_STATUS_SUCCESSECH 成功0SSL_ECH_STATUS_FAILED内部或协议错误-100SSL_ECH_STATUS_BAD_CALL传入参数为 NULL-101SSL_ECH_STATUS_NOT_TRIED未尝试 ECH-102SSL_ECH_STATUS_BAD_NAMEECH 成功但服务端证书校验失败-103SSL_ECH_STATUS_NOT_CONFIGURED未配置 ECH-105SSL_ECH_STATUS_FAILED_ECH尝试失败但收到来自正常名称的 ECH-106SSL_ECH_STATUS_FAILED_ECH_BAD_NAME尝试失败且来自异常名称的 ECH混合联调时的三种典型输出README 给出了三种具有诊断价值的联调场景场景一echecho 客户端 echecho 服务端双方都输出ECH worked (status: 1, ...)如上文所示这是最理想的结果。场景二普通 sslecho 客户端 echecho 服务端服务端检测到客户端没有尝试 ECH输出ECH failed/not-tried (status: -101, inner: (null), outer: (null))状态码-101SSL_ECH_STATUS_NOT_TRIED表示对端根本未发起 ECH 流程。场景三echecho 客户端 普通 sslecho 服务端由于客户端尝试了 ECH 但服务端不支持客户端会以错误退出。在 debug 构建下错误栈类似于80EBEE54227F0000:error:0A000163:SSL routines:tls_process_initial_server_flight:ech required:ssl/statem/statem_clnt.c:3274:该错误ech required发生在 TLS 1.3 客户端状态机处理首条服务器消息的阶段源码位置在 ssl/statem/statem_clnt.c。README 特别说明真实客户端在这种情况下大概率会回退到不使用 ECH 继续连接只是这个示例为演示目的选择了直接报错。与此同时服务端也会因为收到客户端发出的 ECH alertSSL alert number 121而退出403787A8307F0000:error:0A000461:SSL routines:ssl3_read_bytes:reason(1121):../ssl/record/rec_layer_s3.c:1588:SSL alert number 121这两条错误信息分别指向 ssl/statem/statem_clnt.c 与 ssl/record/rec_layer_s3.c可作为排查 ECH 握手失败时的错误定位参考。从示例到真实应用可以借鉴的改造方向综合 README 与源码可以总结出将本示例落地为真实程序的几条路径证书信任链把SSL_CTX_load_verify_locations()换成SSL_CTX_set_default_verify_paths()以使用系统信任库或接入自己的 CA 证书链域名解析客户端目前用inet_pton()只接受点分 IP真实程序应改用getaddrinfo()支持主机名解析并结合SSL_set1_dnsname()做证书名称校验ECH 配置来源echecho的 ECH 数据是硬编码的真实服务端应从文件或密钥管理设施加载 PEM真实客户端应从 DNS HTTPS 记录获取 ECHConfigList超时与并发源码注释见 main.c指出程序尚未为 TCP/SSL 的 accept/read 加入超时机制且服务端是串行处理连接的生产环境需要引入超时控制与并发模型如线程、事件循环或非阻塞 socket 配合SSL_get_error()。小结sslecho以不到 400 行 C 代码完整演示了 OpenSSL TLS 编程的最小闭环SSL_CTX创建 → 证书加载 → TCP 建连 →SSL_new/SSL_set_fd→SSL_connect/SSL_accept握手 →SSL_read/SSL_write通信 →SSL_shutdown清理。而它的 ECH 变体echecho进一步展示了 ECH 与普通 TLS 代码之间的最小差异——只需加载 ECH 数据并调用SSL_CTX_set1_echstore()配合SSL_ech_get1_status()即可验证加密 ClientHello 是否生效。对于想快速上手 OpenSSL 编程或调研 ECH 落地细节的开发者而言这两个示例与 include/openssl/ech.h、doc/man3/SSL_set1_echstore.pod 构成了一套完整、可直接对照学习的参考材料。【免费下载链接】opensslGeneral purpose TLS and crypto library项目地址: https://gitcode.com/GitHub_Trending/ope/openssl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表