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 更新
这个提交包含在:
@@ -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 字节,常量时间标量乘;非整字节对齐,标量乘按大端位遍历)。
|
||||
|
||||
@@ -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 <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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 连接对象
|
||||
* ============================================================ */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 给长度。
|
||||
|
||||
在新工单中引用
屏蔽一个用户