From 5c5517821fa880ec65f56243f915fed5bec64708 Mon Sep 17 00:00:00 2001 From: JGZYES Date: Thu, 13 Aug 2026 19:51:55 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20TLS=201.3=20ECDSA=20?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=E4=BA=92=E6=93=8D=E4=BD=9C:=20CertificateVer?= =?UTF-8?q?ify=20=E6=94=B9=E7=94=A8=20DER=20=E7=AD=BE=E5=90=8D(RFC=208446?= =?UTF-8?q?=204.2.3),=20=E8=A1=A5=E9=BD=90=20NewSessionTicket=20extensions?= =?UTF-8?q?,=20=E6=94=AF=E6=8C=81=20SHA-384=20=E5=A5=97=E4=BB=B6=E4=B8=8E?= =?UTF-8?q?=20X.509=20ECDSA=20=E8=AF=81=E4=B9=A6=E9=AA=8C=E7=AD=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- README.md | 2 +- STATUS.md | 10 +++---- src/crypto/ecdsa.c | 59 +++++++++++++++++++++++++++------------ src/ssl/tls_handshake13.c | 18 ++++++++++-- 4 files changed, 62 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 628ce2c..3bb85b6 100644 --- a/README.md +++ b/README.md @@ -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` 双向互操作对测。 diff --git a/STATUS.md b/STATUS.md index 09e7ccd..fa55431 100644 --- a/STATUS.md +++ b/STATUS.md @@ -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/4:s_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/6:s_server↔s_client 双向 TLS 1.3 / TLS 1.2(RSA 与 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 CertificateVerify(RFC 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 数据路径与 HRR(HelloRetryRequest)未实现 diff --git a/src/crypto/ecdsa.c b/src/crypto/ecdsa.c index f1f655e..4b5e1c7 100644 --- a/src/crypto/ecdsa.c +++ b/src/crypto/ecdsa.c @@ -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 包装 ---- */ diff --git a/src/ssl/tls_handshake13.c b/src/ssl/tls_handshake13.c index ba8c1c1..02bb8bc 100644 --- a/src/ssl/tls_handshake13.c +++ b/src/ssl/tls_handshake13.c @@ -10,6 +10,7 @@ */ #include "tls_internal.h" #include +#include /* ============================================================ * [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 */