修复 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
这个提交包含在:
@@ -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` 双向互操作对测。
|
||||
|
||||
@@ -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)未实现
|
||||
|
||||
+37
-14
@@ -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,
|
||||
/* 核心签名逻辑(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,
|
||||
uint8_t *sig, size_t *siglen) {
|
||||
if (!k || !mhash || !sig || !siglen) return PAZE_ERR_INVALID;
|
||||
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,
|
||||
/* 核心验签逻辑: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 uint8_t *sig, size_t siglen) {
|
||||
if (!pub || !mhash || !sig) return PAZE_ERR_INVALID;
|
||||
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 */
|
||||
|
||||
在新工单中引用
屏蔽一个用户