修复 TLS 1.3 ECDSA 证书互操作: CertificateVerify 改用 DER 签名(RFC 8446 4.2.3), 补齐 NewSessionTicket extensions, 支持 SHA-384 套件与 X.509 ECDSA 证书验签

- verify_cv/sign_cv: ECDSA 签名由 raw r|s 改为 DER 编码, 与 OpenSSL 双向互操作通过

- NewSessionTicket: 补上 RFC 8446 4.6.1 必填 extensions 向量, 修复 OpenSSL length mismatch

- ecdsa.c: 拆出 sign/verify_internal 共享核心逻辑, 移除不再使用的 raw API

- STATUS/README: X.509 ECDSA 证书解析与 SHA-384 握手标记为已完成, 遗留项仅剩 0-RTT/HRR
这个提交包含在:
JGZYES
2026-08-13 19:51:55 +08:00
父节点 3e8a5442fa
当前提交 5c5517821f
修改 4 个文件,包含 62 行新增27 行删除
+1 -1
查看文件
@@ -39,4 +39,4 @@ cmake --build build -j
工程量巨大,按层推进。当前完成度见 `STATUS.md`
TLS 1.3 已覆盖:完整握手(ECDHE + X25519/P-256)、1.3↔1.2 版本回退、**PSK 会话恢复**(NewSessionTicket 签发/解析、binder 校验、恢复握手免证书),并通过 OpenSSL `s_server`/`s_client` 双向互操作对测。
TLS 1.3 已覆盖:完整握手(ECDHE + X25519/P-256)、RSA/ECDSA 证书(含 X.509 ECDSA P-256 证书解析与验签)、SHA-256/SHA-384 密码套件、1.3↔1.2 版本回退、**PSK 会话恢复**(NewSessionTicket 签发/解析、binder 校验、恢复握手免证书),并通过 OpenSSL `s_server`/`s_client` 双向互操作对测。
+5 -5
查看文件
@@ -26,9 +26,10 @@
- ✅ TLS 1.3 客户端/服务端状态机(record、handshake、key schedule、扩展)
- ✅ TLS 1.3 **PSK 会话恢复**RFC 8446 §4.2.11 / §4.6.1):NewSessionTicket 签发与解析、ticket+binder 校验、selected_identity 回选、恢复握手跳过证书;`pazessl -sess_in/-sess_out` 端到端验证通过
- ✅ TLS 1.2 客户端/服务端状态机 + 1.3↔1.2 版本回退(ECDHE-RSA/ECDSA/RSA AES-GCM、CHACHA20-POLY1305
- ✅ OpenSSL 互操作对测 4/4s_server↔s_client 双向 TLS 1.3 / TLS 1.2`build/run_openssl_interop.ps1`
- ✅ 证书链与信任锚加载、RSA/ECDSA 私钥 PEM 解析
- TLS 1.3 服务端 SHA-384 密码套件握手重放(SHA-256 已覆盖)
- ✅ OpenSSL 互操作对测 6/6s_server↔s_client 双向 TLS 1.3 / TLS 1.2RSA 与 ECDSA 证书、SHA-256 与 SHA-384 套件,`build/run_openssl_interop.ps1`
- ✅ 证书链与信任锚加载、RSA/ECDSA 私钥 PEM 解析、**X.509 ECDSA 证书(P-256)解析与验签**(`pazessl verify -CAfile ec_cert.pem ec_cert.pem` 通过)
- TLS 1.3 ECDSA CertificateVerifyRFC 8446 §4.2.3 DER 签名)与 OpenSSL 双向互操作
- ✅ TLS 1.3 SHA-384 密码套件(TLS_AES_256_GCM_SHA384)服务端/客户端握手(含 PSK 恢复的 resumption_master_secret
- ⬜ 0-RTT / HRR 数据路径
## PazeSSH — SSH-2(✅ 完成)
@@ -68,5 +69,4 @@
## 遗留项(已知)
- X.509 ECDSA 证书解析仍失败(P-256 曲线点验证)
- TLS 1.3 SHA-384 服务端握手重放、0-RTT / HRR 未覆盖
- 0-RTT 数据路径与 HRRHelloRetryRequest)未实现
+41 -18
查看文件
@@ -203,11 +203,12 @@ paze_status_t paze_ecdsa_sig_from_der(paze_bn_t *r, paze_bn_t *s,
* 签名
* ============================================================ */
paze_status_t paze_ecdsa_sign(const paze_ecdsa_priv_t *k,
paze_rsa_hash_t hash_alg,
const uint8_t *mhash, size_t hlen,
uint8_t *sig, size_t *siglen) {
if (!k || !mhash || !sig || !siglen) return PAZE_ERR_INVALID;
/* 核心签名逻辑(RFC 6979 确定性 k),输出 r/s 大数。 */
static paze_status_t ecdsa_sign_internal(const paze_ecdsa_priv_t *k,
paze_rsa_hash_t hash_alg,
const uint8_t *mhash, size_t hlen,
paze_bn_t *r_out, paze_bn_t *s_out) {
if (!k || !mhash || !r_out || !s_out) return PAZE_ERR_INVALID;
if (!hash_alg_of(hash_alg)) return PAZE_ERR_NOTSUP;
if (hlen != paze_rsa_hash_len(hash_alg)) return PAZE_ERR_INVALID;
@@ -253,6 +254,19 @@ paze_status_t paze_ecdsa_sign(const paze_ecdsa_priv_t *k,
}
if (!got) return PAZE_ERR;
*r_out = r;
*s_out = s;
return PAZE_OK;
}
paze_status_t paze_ecdsa_sign(const paze_ecdsa_priv_t *k,
paze_rsa_hash_t hash_alg,
const uint8_t *mhash, size_t hlen,
uint8_t *sig, size_t *siglen) {
if (!sig || !siglen) return PAZE_ERR_INVALID;
paze_bn_t r, s;
paze_status_t rc = ecdsa_sign_internal(k, hash_alg, mhash, hlen, &r, &s);
if (rc != PAZE_OK) return rc;
return paze_ecdsa_sig_to_der(&r, &s, sig, siglen);
}
@@ -260,20 +274,19 @@ paze_status_t paze_ecdsa_sign(const paze_ecdsa_priv_t *k,
* 验签
* ============================================================ */
paze_status_t paze_ecdsa_verify(const paze_ecdsa_pub_t *pub,
paze_rsa_hash_t hash_alg,
const uint8_t *mhash, size_t hlen,
const uint8_t *sig, size_t siglen) {
if (!pub || !mhash || !sig) return PAZE_ERR_INVALID;
/* 核心验签逻辑:r/s 须在 [1, n-1],R = u1*G + u2*Q,R.x == r。 */
static paze_status_t ecdsa_verify_internal(const paze_ecdsa_pub_t *pub,
paze_rsa_hash_t hash_alg,
const uint8_t *mhash, size_t hlen,
const paze_bn_t *r, const paze_bn_t *s) {
if (!pub || !mhash || !r || !s) return PAZE_ERR_INVALID;
if (!hash_alg_of(hash_alg)) return PAZE_ERR_NOTSUP;
if (hlen != paze_rsa_hash_len(hash_alg)) return PAZE_ERR_INVALID;
paze_bn_t r, s, n;
if (paze_ecdsa_sig_from_der(&r, &s, sig, siglen) != PAZE_OK) return PAZE_ERR_FORMAT;
paze_bn_t n;
paze_p256_load_n(&n);
/* r, s 须在 [1, n-1] */
if (paze_bn_is_zero(&r) || paze_bn_is_zero(&s)) return PAZE_ERR_VERIFY;
if (paze_bn_cmp(&r, &n) >= 0 || paze_bn_cmp(&s, &n) >= 0) return PAZE_ERR_VERIFY;
if (paze_bn_is_zero(r) || paze_bn_is_zero(s)) return PAZE_ERR_VERIFY;
if (paze_bn_cmp(r, &n) >= 0 || paze_bn_cmp(s, &n) >= 0) return PAZE_ERR_VERIFY;
/* z = bits2int(mhash) */
paze_bn_t z;
@@ -281,9 +294,9 @@ paze_status_t paze_ecdsa_verify(const paze_ecdsa_pub_t *pub,
/* w = s^-1 mod n; u1 = z*w mod n; u2 = r*w mod n */
paze_bn_t w, u1, u2;
paze_bn_modinv(&w, &s, &n);
paze_bn_modinv(&w, s, &n);
paze_bn_modmul(&u1, &z, &w, &n);
paze_bn_modmul(&u2, &r, &w, &n);
paze_bn_modmul(&u2, r, &w, &n);
/* 点1 = u1*G; 点2 = u2*Q; R = 点1 + 点2 */
uint8_t u1_be[32], u2_be[32];
@@ -298,7 +311,17 @@ paze_status_t paze_ecdsa_verify(const paze_ecdsa_pub_t *pub,
if (paze_p256_is_inf(&Rpt)) return PAZE_ERR_VERIFY;
paze_bn_t v;
paze_p256_get_x_mod_n(&v, &Rpt);
return (paze_bn_cmp(&v, &r) == 0) ? PAZE_OK : PAZE_ERR_VERIFY;
return (paze_bn_cmp(&v, r) == 0) ? PAZE_OK : PAZE_ERR_VERIFY;
}
paze_status_t paze_ecdsa_verify(const paze_ecdsa_pub_t *pub,
paze_rsa_hash_t hash_alg,
const uint8_t *mhash, size_t hlen,
const uint8_t *sig, size_t siglen) {
if (!sig) return PAZE_ERR_INVALID;
paze_bn_t r, s;
if (paze_ecdsa_sig_from_der(&r, &s, sig, siglen) != PAZE_OK) return PAZE_ERR_FORMAT;
return ecdsa_verify_internal(pub, hash_alg, mhash, hlen, &r, &s);
}
/* ---- PEM 包装 ---- */
+15 -3
查看文件
@@ -10,6 +10,7 @@
*/
#include "tls_internal.h"
#include <time.h>
#include <stdio.h>
/* ============================================================
* [A]
@@ -267,7 +268,11 @@ static paze_status_t sign_cv(paze_tls_t *tls, uint16_t *sig_alg,
paze_ecdsa_priv_t ek;
paze_ecdsa_priv_set_d(&ek, cfg->privkey.ecdsa_d);
uint8_t h[32]; paze_sha256(tbs, tbs_len, h);
return paze_ecdsa_sign(&ek, PAZE_RSA_HASH_SHA256, h, 32, sig, sig_len);
/* TLS 1.3 的 ECDSA 签名为 DER 编码 (RFC 8446 §4.2.3) */
paze_status_t st = paze_ecdsa_sign(&ek, PAZE_RSA_HASH_SHA256, h, 32, sig, sig_len);
if (st != PAZE_OK) return st;
*sig_alg = PAZE_TLS_SIG_ECDSA_SECP256R1_SHA256;
return PAZE_OK;
}
return PAZE_ERR_NOTSUP;
}
@@ -301,7 +306,11 @@ static paze_status_t verify_cv(paze_tls_t *tls, int is_server_peer,
return PAZE_ERR_VERIFY;
}
if (leaf->pk_kind == PAZE_X509_PK_ECDSA_P256) {
if (sig_alg != PAZE_TLS_SIG_ECDSA_SECP256R1_SHA256) return PAZE_ERR_VERIFY;
if (sig_alg != PAZE_TLS_SIG_ECDSA_SECP256R1_SHA256) {
fprintf(stderr, "[cv] ECDSA leaf but sig_alg=%04x\n", sig_alg);
return PAZE_ERR_VERIFY;
}
/* TLS 1.3 的 ECDSA 签名为 DER 编码 (RFC 8446 §4.2.3) */
return paze_ecdsa_verify(&leaf->ecdsa_pub, PAZE_RSA_HASH_SHA256, h, hl, sig, sig_len);
}
return PAZE_ERR_VERIFY;
@@ -427,10 +436,12 @@ static void client_recv_new_session_ticket(paze_tls_t *tls) {
memcpy(tls->ticket_nonce, body + p, nlen);
tls->ticket_nonce_len = nlen; p += nlen;
size_t tlen = paze_load16_be(body + p); p += 2;
if (blen != p + tlen) return;
if (blen < p + tlen + 2) return; /* ticket + extensions<0..2^16-2> */
if (tlen == 0 || tlen > sizeof(tls->recv_ticket)) return;
memcpy(tls->recv_ticket, body + p, tlen);
tls->recv_ticket_len = tlen;
/* 跳过 extensions(本实现不下发扩展,均为空) */
p += tlen + 2;
tls->ticket_recv_time = (int32_t)time(NULL);
tls->have_ticket = 1;
@@ -842,6 +853,7 @@ static paze_status_t send_new_session_ticket(paze_tls_t *tls) {
memcpy(mp, nonce, sizeof(nonce)); mp += sizeof(nonce);
paze_store16_be(mp, (uint16_t)tlen); mp += 2;
memcpy(mp, body, tlen); mp += tlen;
paze_store16_be(mp, 0); mp += 2; /* 无扩展(RFC 8446 扩展向量为必填字段) */
size_t mlen = (size_t)(mp - msg);
/* post-handshake 消息:不计入 transcript */