TLS 1.3 会话恢复持久化: session ticket key 支持落盘(新增 paze_tls_config_set_session_ticket_file 与 paze_tls_ensure_ticket_key, 生成/校验入口加载), 服务端跨进程重启后会话恢复仍有效; pazessl s_server -ticket_file; 端到端验证(重启后 resumed=YES); STATUS/AGENT/CHANGELOG 更新

这个提交包含在:
JGZYES
2026-08-14 12:43:42 +08:00
父节点 2504d32d3b
当前提交 225a339a0e
修改 8 个文件,包含 59 行新增6 行删除
+4
查看文件
@@ -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 <file>`,端到端验证(重启后 resumed=YES)。
+5
查看文件
@@ -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 <file>`。端到端验证:重启服务端(同 ticket 文件)后 `resumed=YES`
### 新增 —— TLS secp521r1 (P-521) 完整链路
- 新增 P-521 椭圆曲线:`src/crypto/p521.c` + `include/paze/p521.h`(域 p = 2^521-1,标量/仿射点 66/133 字节,常量时间标量乘;非整字节对齐,标量乘按大端位遍历)。
+1
查看文件
@@ -33,6 +33,7 @@
-**HRRHelloRetryRequest**RFC 8446 §4.1.3/§4.1.4):服务端 key_share 不匹配时发 HRR(随机数=固定魔数)、transcript 合成 MessageHash 重放、客户端 CH2(仅选定组 share)响应、HRR 后放弃 0-RTT;`-key_share` 选项触发,`hrr=YES` 互操作验证通过
-**secp384r1TLS 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 双向交叉验证
-**secp521r1TLS 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_SHA5120x0603),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 <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(✅ 完成)
+4 -1
查看文件
@@ -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
+6
查看文件
@@ -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);
+29
查看文件
@@ -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;
}
/* ============================================================
*
* ============================================================ */
+4 -5
查看文件
@@ -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;
+6
查看文件
@@ -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