From 225a339a0e4f3cb92886b946825ff4e6b5995187 Mon Sep 17 00:00:00 2001 From: JGZYES Date: Fri, 14 Aug 2026 12:43:42 +0800 Subject: [PATCH] =?UTF-8?q?TLS=201.3=20=E4=BC=9A=E8=AF=9D=E6=81=A2?= =?UTF-8?q?=E5=A4=8D=E6=8C=81=E4=B9=85=E5=8C=96:=20session=20ticket=20key?= =?UTF-8?q?=20=E6=94=AF=E6=8C=81=E8=90=BD=E7=9B=98(=E6=96=B0=E5=A2=9E=20pa?= =?UTF-8?q?ze=5Ftls=5Fconfig=5Fset=5Fsession=5Fticket=5Ffile=20=E4=B8=8E?= =?UTF-8?q?=20paze=5Ftls=5Fensure=5Fticket=5Fkey,=20=E7=94=9F=E6=88=90/?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E5=85=A5=E5=8F=A3=E5=8A=A0=E8=BD=BD),=20?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=AB=AF=E8=B7=A8=E8=BF=9B=E7=A8=8B=E9=87=8D?= =?UTF-8?q?=E5=90=AF=E5=90=8E=E4=BC=9A=E8=AF=9D=E6=81=A2=E5=A4=8D=E4=BB=8D?= =?UTF-8?q?=E6=9C=89=E6=95=88;=20pazessl=20s=5Fserver=20-ticket=5Ffile;=20?= =?UTF-8?q?=E7=AB=AF=E5=88=B0=E7=AB=AF=E9=AA=8C=E8=AF=81(=E9=87=8D?= =?UTF-8?q?=E5=90=AF=E5=90=8E=20resumed=3DYES);=20STATUS/AGENT/CHANGELOG?= =?UTF-8?q?=20=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENT.MD | 4 ++++ CHANGELOG.md | 5 +++++ STATUS.md | 1 + apps/pazessl/main.c | 5 ++++- include/paze/tls.h | 6 ++++++ src/ssl/tls_conn.c | 29 +++++++++++++++++++++++++++++ src/ssl/tls_handshake13.c | 9 ++++----- src/ssl/tls_internal.h | 6 ++++++ 8 files changed, 59 insertions(+), 6 deletions(-) diff --git a/AGENT.MD b/AGENT.MD index b645726..3c5b551 100644 --- a/AGENT.MD +++ b/AGENT.MD @@ -78,3 +78,7 @@ STATUS.md 完成度总表 AGENT.md 本文件 - CLI:`s_client -key_share P-521`。 - 验证:曲线(n·G=∞、3G=2G+G)、ECDSA 双向、X.509 证书 OpenSSL verify=OK、TLS 端点(密钥交换 hrr=YES+证书链/主机名验证)全部通过;既有自测无回归。 +### 5. TLS 1.3 会话恢复持久化(✅ 已完成 2026-08-14) + +session ticket key 支持持久化到文件:新增 `paze_tls_config_set_session_ticket_file`(设文件路径)与 `paze_tls_ensure_ticket_key`(首次生成时读取/写盘;PSK 验证入口 `server_try_psk` 亦调用以加载),实现服务端**跨进程重启**后会话恢复仍然有效。`pazessl s_server -ticket_file `,端到端验证(重启后 resumed=YES)。 + diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ff8188..3d2ae78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ## [开发主线] — 2026-08-14 +### 新增 —— TLS 1.3 会话恢复持久化 + +- session ticket key:新增 `paze_tls_config_set_session_ticket_file`(持久化文件)与 `paze_tls_ensure_ticket_key`(首次生成时读取/写盘,PSK 验证入口亦加载),服务端**跨进程重启**后 ticket 仍可校验、会话恢复继续有效。 +- CLI:`pazessl s_server -ticket_file `。端到端验证:重启服务端(同 ticket 文件)后 `resumed=YES`。 + ### 新增 —— TLS secp521r1 (P-521) 完整链路 - 新增 P-521 椭圆曲线:`src/crypto/p521.c` + `include/paze/p521.h`(域 p = 2^521-1,标量/仿射点 66/133 字节,常量时间标量乘;非整字节对齐,标量乘按大端位遍历)。 diff --git a/STATUS.md b/STATUS.md index 4da2c88..e618c46 100644 --- a/STATUS.md +++ b/STATUS.md @@ -33,6 +33,7 @@ - ✅ **HRR(HelloRetryRequest)**(RFC 8446 §4.1.3/§4.1.4):服务端 key_share 不匹配时发 HRR(随机数=固定魔数)、transcript 合成 MessageHash 重放、客户端 CH2(仅选定组 share)响应、HRR 后放弃 0-RTT;`-key_share` 选项触发,`hrr=YES` 互操作验证通过 - ✅ **secp384r1(TLS P-384)**:新增 P-384 曲线点运算(`p384.c`,Jacobian,常量时间标量乘);ECDSA 支持 P-384 签名/验签(RFC 6979 确定性 k,`paze_ecdsa_p384_*`);X.509 支持 secp384r1 证书解析/验签/签发(含 ecdsa-with-SHA384 与 P-384 公钥,`paze_x509_build_spki384`);TLS 1.3 支持 secp384r1 组(key_share/supported_groups/ECDH 按 SEC1 x 坐标)、签名算法 ECDSA_SECP384R1_SHA384;TLS 1.2 ECDHE 亦支持 P-384。`s_client -key_share P-384` 端到端验证(含 HRR 触发与 P-384 证书链/主机名验证);曲线与 ECDSA 经 OpenSSL 双向交叉验证 - ✅ **secp521r1(TLS P-521)**:新增 P-521 曲线点运算(`p521.c`,域 p=2^521-1,标量/点 66/133 字节,常量时间标量乘);ECDSA 支持 P-521 签名/验签(RFC 6979,配 SHA-512,`paze_ecdsa_p521_*`);X.509 支持 secp521r1 证书解析/验签/签发(含 ecdsa-with-SHA512,`paze_x509_build_spki521`);TLS 1.3 支持 secp521r1 组与签名算法 ECDSA_SECP521R1_SHA512(0x0603),TLS 1.2 ECDHE 亦支持。`s_client -key_share P-521` 端到端验证(HRR + P-521 证书链/主机名验证,应用数据收发);曲线(n·G=∞)、ECDSA 双向、X.509 证书均经 OpenSSL 交叉验证 +- ✅ **TLS 1.3 会话恢复持久化**:session ticket key 支持持久化到文件(`paze_tls_config_set_session_ticket_file` + `paze_tls_ensure_ticket_key`),服务端跨进程重启后 ticket 仍可校验、PSK 会话恢复继续有效;`pazessl s_server -ticket_file ` 端到端验证(重启后 resumed=YES) - ✅ **修复**:ECDSA `bits2int` 输入短于群阶时错误左移(影响 P-384+SHA-256 验签);TLS 1.3/1.2 对端证书 `issuer_dn/subject_dn` 等指针指向被记录层复用缓冲导致的链验证失败(`paze_tls_store_peer_cert` 拷贝 DER) ## PazeSSH — SSH-2(✅ 完成) diff --git a/apps/pazessl/main.c b/apps/pazessl/main.c index 18683be..42ccdd1 100644 --- a/apps/pazessl/main.c +++ b/apps/pazessl/main.c @@ -73,7 +73,7 @@ static int usage(void) { "Usage:\n" " pazessl s_client -connect host:port [-CAfile roots.pem] [-servername SNI] [-tls1_2]\n" " [-sess_in file] [-sess_out file] [-msg text] [-early] [-key_share X25519|P-256|P-384|P-521]\n" - " pazessl s_server -accept port -cert cert.pem -key key.pem [-tls1_2] [-reuse N] [-early]\n" + " pazessl s_server -accept port -cert cert.pem -key key.pem [-tls1_2] [-reuse N] [-early] [-ticket_file f]\n" " pazessl verify -CAfile roots.pem cert.pem\n" " pazessl genrsa -out key.pem [2048]\n" " pazessl req -x509 -newkey rsa:2048 -keyout k.pem -out c.pem -days D -subj /CN=X\n" @@ -378,6 +378,7 @@ int main(int argc, char **argv) { const char *certf = NULL; const char *keyf = NULL; int only_12 = 0, do_early = 0; int reuse = 1; + const char *ticketfile = NULL; for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "-accept") == 0 && i + 1 < argc) port = atoi(argv[++i]); else if (strcmp(argv[i], "-cert") == 0 && i + 1 < argc) certf = argv[++i]; @@ -385,6 +386,7 @@ int main(int argc, char **argv) { else if (strcmp(argv[i], "-reuse") == 0 && i + 1 < argc) reuse = atoi(argv[++i]); else if (strcmp(argv[i], "-tls1_2") == 0) only_12 = 1; else if (strcmp(argv[i], "-early") == 0) do_early = 1; + else if (strcmp(argv[i], "-ticket_file") == 0 && i + 1 < argc) ticketfile = argv[++i]; else if (strcmp(argv[i], "-v") == 0) paze_log_set_level(PAZE_LOG_DEBUG); } if (!certf || !keyf) { @@ -404,6 +406,7 @@ int main(int argc, char **argv) { if (do_early) paze_tls_config_enable_early_data(cfg, 1); paze_tls_config_set_cert_chain(cfg, (const char *)cdata, (const char *)kdata); paze_tls_config_set_verify(cfg, PAZE_TLS_VERIFY_NONE, NULL, NULL); + if (ticketfile) paze_tls_config_set_session_ticket_file(cfg, ticketfile); /* Listen */ #ifdef _WIN32 diff --git a/include/paze/tls.h b/include/paze/tls.h index 22d0f8c..203b7a1 100644 --- a/include/paze/tls.h +++ b/include/paze/tls.h @@ -249,6 +249,12 @@ paze_status_t paze_tls_get_session_ticket_key(const paze_tls_t *tls, paze_status_t paze_tls_config_set_session_ticket_key(paze_tls_config_t *cfg, const uint8_t key[32]); +/* 服务端:将 session ticket key 持久化到指定文件。服务端首次生成 key 时会 + * 尝试从该文件加载(存在且 32 字节),否则生成并写盘;实现跨进程重启后 + * 会话恢复仍然有效。 */ +paze_status_t paze_tls_config_set_session_ticket_file(paze_tls_config_t *cfg, + const char *path); + paze_tls_t *paze_tls_new(const paze_tls_config_t *cfg); void paze_tls_free(paze_tls_t *tls); diff --git a/src/ssl/tls_conn.c b/src/ssl/tls_conn.c index 3e3c07c..86da316 100644 --- a/src/ssl/tls_conn.c +++ b/src/ssl/tls_conn.c @@ -312,6 +312,35 @@ paze_status_t paze_tls_config_set_session_ticket_key(paze_tls_config_t *cfg, return PAZE_OK; } +paze_status_t paze_tls_config_set_session_ticket_file(paze_tls_config_t *cfg, + const char *path) { + if (!cfg || !path) return PAZE_ERR_INVALID; + size_t l = strlen(path); + if (l >= sizeof(cfg->ticket_key_file)) return PAZE_ERR_OVERFLOW; + memcpy(cfg->ticket_key_file, path, l + 1); + return PAZE_OK; +} + +/* 服务端首次需要 ticket key 时调用:若配置了持久化文件则加载/生成并写盘。 */ +paze_status_t paze_tls_ensure_ticket_key(paze_tls_config_t *cfg) { + if (cfg->ticket_key_ready) return PAZE_OK; + if (cfg->ticket_key_file[0]) { + FILE *f = fopen(cfg->ticket_key_file, "rb"); + if (f) { + size_t n = fread(cfg->ticket_key, 1, 32, f); + fclose(f); + if (n == 32) { cfg->ticket_key_ready = 1; return PAZE_OK; } + } + } + if (paze_random(cfg->ticket_key, 32) != PAZE_OK) return PAZE_ERR_PLATFORM; + cfg->ticket_key_ready = 1; + if (cfg->ticket_key_file[0]) { + FILE *f = fopen(cfg->ticket_key_file, "wb"); + if (f) { fwrite(cfg->ticket_key, 1, 32, f); fclose(f); } + } + return PAZE_OK; +} + /* ============================================================ * 连接对象 * ============================================================ */ diff --git a/src/ssl/tls_handshake13.c b/src/ssl/tls_handshake13.c index b99cdef..f8a8aa7 100644 --- a/src/ssl/tls_handshake13.c +++ b/src/ssl/tls_handshake13.c @@ -1118,6 +1118,8 @@ static int server_try_psk(paze_tls_t *tls, const uint8_t *exts, size_t exts_len, size_t pl = id[2]; if (idlen != 3 + pl + 4 + PAZE_TLS_TICKET_MAC_LEN) { PAZE_LOGD("psk: id len mismatch %zu vs %zu", idlen, 3 + pl + 4 + PAZE_TLS_TICKET_MAC_LEN); return 0; } if (pl == 0 || pl > sizeof(tls->psk)) { PAZE_LOGD("psk: bad pl %zu", pl); return 0; } + /* 跨进程重启后 ticket_key 初始未就绪:若配置了持久化文件则从文件加载 */ + paze_tls_ensure_ticket_key(cfgc); if (!cfgc->ticket_key_ready) { PAZE_LOGD("psk: ticket_key not ready"); return 0; } uint8_t mac[32]; paze_hmac(paze_hash_sha256(), cfgc->ticket_key, 32, id, 3 + pl + 4, mac); @@ -1198,12 +1200,9 @@ static paze_status_t send_new_session_ticket(paze_tls_t *tls) { if (st != PAZE_OK) return st; /* 构造 ticket(认证封装)。ticket_key 存于 cfg:同一 cfg 的多连接共享, - * 实现跨连接会话恢复。 */ + * 实现跨连接会话恢复;若配置了 ticket 文件则跨进程持久化。 */ paze_tls_config_t *cfgc = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg; - if (!cfgc->ticket_key_ready) { - if (paze_random(cfgc->ticket_key, 32) != PAZE_OK) return PAZE_ERR_PLATFORM; - cfgc->ticket_key_ready = 1; - } + if (paze_tls_ensure_ticket_key(cfgc) != PAZE_OK) return PAZE_ERR_PLATFORM; uint8_t body[512]; size_t bp = 0; body[bp++] = PAZE_TLS_TICKET_VER; body[bp++] = (uint8_t)tls->psk_hash; diff --git a/src/ssl/tls_internal.h b/src/ssl/tls_internal.h index 9480b99..2c6e987 100644 --- a/src/ssl/tls_internal.h +++ b/src/ssl/tls_internal.h @@ -143,6 +143,9 @@ struct paze_tls_config { * 即可实现跨连接会话恢复。 */ uint8_t ticket_key[32]; int ticket_key_ready; + /* 若非空:session ticket key 持久化到该文件(服务端首次生成时读取/写盘), + * 使跨进程重启仍然有效。 */ + char ticket_key_file[1024]; /* 0-RTT: 客户端启用(服务端默认接受,1=接受) */ int enable_early; @@ -331,6 +334,9 @@ paze_status_t paze_tls_bio_read(paze_tls_t *tls, uint8_t *buf, size_t n); /* 把 n 字节全部写入 BIO */ paze_status_t paze_tls_bio_write_all(paze_tls_t *tls, const uint8_t *buf, size_t n); +/* 确保 session ticket key 就绪:若配置了持久化文件则加载/生成并写盘 */ +paze_status_t paze_tls_ensure_ticket_key(paze_tls_config_t *cfg); + /* ---- tls_record.c ---- */ /* 读取并(若已加密)解密下一条 record。 * out 指向明文(可能指向 tls->in_buf 内部或 app_buf),outlen 给长度。