diff --git a/AGENT.MD b/AGENT.MD index 9253ada..e47cb40 100644 --- a/AGENT.MD +++ b/AGENT.MD @@ -1,500 +1,42 @@ -# PazeSSL / PazeSSH 项目代理规范 (AGENT.MD) +# AGENT.md — 接手指南 -> **版本**: v0.5 (2026-08-13) — TLS 1.2 完成(版本回退 + OpenSSL 互操作 4/4)+ X.509 RSA round-trip 修复 + PEM 密钥加载 + 多证书链支持 -> **目标**: 用纯 libc + 自底向上 C 语言实现 **OpenSSL(平替 PazeSSL) ≤ 100,000 行** 与 **OpenSSH(平替 PazeSSH) ≤ 30,000 行**。 -> **约束**: 仅使用libc 要能在裸机上实现,**禁止**依赖 OpenSSL / LibreSSL / libcrypto / mbedTLS / libgcrypt / libssh 等外部密码学或协议库。 - ---- - -## 一、总体架构 +## 项目结构 ``` -┌─────────────────────────────────────────────────────────────────┐ -│ apps/ CLI 层 │ -│ pazessl/ SSH/ sshd/ │ -│ (s_client/s_server s_time verify x509) (ssh client) (sshd) │ -├──────────┬──────────────────────────────────────────────────────┤ -│ src/ssl │ src/ssh │ -│ 100k预算 │ 30k预算 │ -│ │ │ -│ tls_bio │ ssh_packet (binary packet 二进制包层) │ -│ tls_record │ ssh_transport (transport layer 传输层) │ -│ tls_transcript │ ssh_kex (密钥交换) │ -│ tls_keys │ ssh_keys (主机键/公钥) │ -│ tls_extensions │ ssh_auth (用户认证:pw/pk/hostbased) │ -│ tls_handshake13 │ ssh_channel (会话/shell/exec 通道) │ -│ tls_handshake12 │ ssh_client (客户端入口) │ -│ tls_client │ ssh_server (sshd 入口) │ -│ tls_server │ │ -│ tls_conn │ │ -├──────────┴──────────────────────────────────────────────────────┤ -│ src/crypto (≈7000 行,已完成) │ -│ hash (SHA1/224/256/384/512, MD5) │ -│ HMAC / HKDF / PBKDF2 │ -│ AES-128/256 (ECB/CBC/CTR/GCM) AEAD │ -│ ChaCha20 / Poly1305 / ChaCha20-Poly1305 AEAD │ -│ bignum (Montgomery 模幂 / 模乘 / 模逆,最大 512 limb=2KB) │ -│ RSA PKCS#1 v1.5 + PSS (加密/解密/签名/验签/CRT私钥/密钥生成) │ -│ X25519 (曲线25519蒙哥马利梯) Ed25519 (EdDSA) │ -│ P-256 (secp256r1 Jacobian 点运算, ECDSA) │ -│ ASN.1 DER 读写器 / PEM 编解码 / Base64 / Hex │ -│ X.509 v3 (DER/PEM 解析、签名验证、链验证、SAN、自签生成) │ -├─────────────────────────────────────────────────────────────────┤ -│ src/util (mem/error/log/version) + include/paze/*.h │ -├─────────────────────────────────────────────────────────────────┤ +include/paze/ 公共头文件(tls.h 对外 API) +src/crypto/ 密码学原语:哈希/AES/ChaCha20/Poly1305/AEAD/X25519/Ed25519/bignum/RSA/P-256/KDF/ASN.1/X.509 +src/ssl/ TLS 1.2+1.3:tls_record(记录层) tls_keys(key schedule) tls_extensions(扩展) + tls_handshake13/12(状态机) tls_transcript tls_conn(API/生命周期) tls_client/server(入口) +src/ssh/ SSH-2:transport/kex/auth/channel/client/server/sftp +src/util/ 内存/字节序/错误/日志 +apps/ 命令行:pazessl / pssh(分发) / 独立命令(pscp/pkeygen/psshd/psftp…) +build/ 中间产物(编译)/测试脚本/互操作脚本 +bin/ 构建输出(exe) +STATUS.md 完成度总表 AGENT.md 本文件 ``` ---- - -## 二、行数预算(严格上限) - -| 层级 / 模块 | 预算 | 已完成(2026-08-12) | 状态 | -|---|---|---|---| -| **crypto + util + include** | **≤ 10,000** | **6,510** | ✅ 完成(含 fe25519.c) | -| **src/ssl (PazeSSL/TLS)** | **≤ 60,000** | **2,328** | ✅ TLS 1.3 完成 (extensions/handshake13/client/server/conn) | -| **src/ssh (PazeSSH/SSH2)** | **≤ 25,000** | **3,779** | ✅ SSH2 全部模块完成 (packet/transport/kex/keys/auth/channel/client/server/scp/agent) | -| **apps/** | ≤ 3,000 | 1,839 | ✅ pssh 单可执行(ssh/keygen/keyscan/copy-id/agent/add/sshd/scp) + pazessl | -| **tests/** | ≤ 2,000 | 1,688 | ✅ paze_kat 统一测试 + 11 个独立 KAT 可执行程序 | -| **合计** | **≤ 100,000** | **16,144** | **83,856 剩余** | - -> **关键红线**: -> - SSL 部分(src/ssl + include/paze/tls.h)绝对不可超过 **60,000 行**。 -> - SSH 部分(src/ssh)绝对不可超过 **25,000 行**。 -> - 任一模块若超限,必须精简:移除冷门特性、压缩 verbose 日志、合并小函数。 - ---- - -## 三、TLS 模块设计 (PazeSSL) - -### 3.1 公共 API (`include/paze/tls.h`, 对外不透明) -```c -typedef struct paze_tls_config paze_tls_config_t; -typedef struct paze_tls paze_tls_t; - -// 配置 -cfg = paze_tls_config_new(PAZE_TLS_CLIENT | PAZE_TLS_SERVER); -paze_tls_config_set_sni(cfg, "example.com"); -paze_tls_config_set_alpn(cfg, {"h2","http/1.1"}, 2); -paze_tls_config_set_cert_chain(cfg, cert_pem, privkey_pem); // server -paze_tls_config_set_trust_roots(cfg, roots_pem); // client -paze_tls_config_set_verify(cfg, PAZE_TLS_VERIFY_REQUIRED, verify_cb, ctx); -paze_tls_config_set_versions(cfg, 1, 1); // 1.3 + 1.2 (默认) -paze_tls_config_free(cfg); - -// 连接 -tls = paze_tls_new(cfg); -paze_tls_set_bio(tls, (paze_tls_bio_t){ctx, read_fn, write_fn}); -paze_status_t s = paze_tls_handshake(tls); // 阻塞循环调用 -int n = paze_tls_read(tls, buf, buflen); -int n = paze_tls_write(tls, buf, n); -paze_tls_close(tls); -paze_tls_free(tls); -``` - -### 3.2 内部模块(已完成 + 待办) -| 文件 | 状态 | 说明 | -|---|---|---| -| tls.h | ✅ 完成 | 公共头:类型、cipher suite、常量、API | -| tls_internal.h | ✅ 完成 | 内部头:paze_tls_t 全字段、各模块共享原型 | -| tls_bio.c | ✅ 完成 | BIO read/write 阻塞循环封装 | -| tls_record.c | ✅ 完成 | 1.3 AEAD(iv xor seq) + 1.2 AEAD-GCM(explicit nonce+seq) | -| tls_transcript.c | ✅ 完成 | Transcript hash + HRR 合成 MessageHash | -| tls_keys.c | ✅ 完成 | HKDF-Expand-Label / Derive-Secret / 1.3 key schedule / 1.2 PRF / cipher_params | -| tls_extensions.c | ✅ 完成 | SNI/ALPN/key_share/supported_groups/sig_algos/supported_versions/renegotiation_info 编码+解析 | -| tls_handshake13.c | ✅ 完成 | TLS 1.3 client+server 状态机:ClientHello/ServerHello/EncryptedExtensions/Certificate/CertificateVerify/Finished (无 PSK/0-RTT) | -| tls_handshake12.c | ✅ 完成 | TLS 1.2 client+server 状态机:ServerHello/Certificate/ServerKeyExchange/CKE/CCS/Finished;含 1.3↔1.2 回退 | -| tls_client.c | ✅ 完成 | 客户端证书验证辅助:链验证+主机名匹配 | -| tls_server.c | ✅ 完成 | 服务端证书有效性检查 | -| tls_conn.c | ✅ 完成 | paze_tls_t 生命周期(new/free/set_bio/read/write/close/handshake) | - -### 3.3 TLS 1.3 握手消息(关键编码,必须严格遵循 RFC 8446) -``` -ClientHello - legacy_version(2)=0x0303, random(32), legacy_session_id(0..32), - cipher_suites(2+n), legacy_compression_methods(1+{0}), extensions(2+n) - EXT: supported_versions(客户端 0x0304/0x0303) - EXT: supported_groups(X25519 + secp256r1) - EXT: signature_algorithms(ecdsa_secp256r1_sha256 / rsa_pss_rsae_sha256 / rsa_pkcs1_sha256) - EXT: key_share(2+ (group 2 + len 2 + key_exchange) × N) - EXT: server_name(host_type=0) - EXT: application_layer_protocol_negotiation - EXT: renegotiation_info(空) - -ServerHello(1.3): - legacy_version=0x0303, random, legacy_session_id_echo(同 CH), - cipher_suite, legacy_compression_method=0, extensions - EXT: supported_versions(选中 0x0304) - EXT: key_share(服务器选中的 share) - -EncryptedExtensions: extensions(任意非握手相关) -Certificate [Request]: - request_context(0/1 bytes) + cert_list(证书序列) - CertificateEntry: cert_data(3+n) + extensions(2+n,含 status_request/OCSP) -CertificateVerify: algorithm(2) + signature(2+n) - 签名内容: "TLS 1.3, server CertificateVerify" + 0x20 × 64 + 0x04 + Transcript-Hash -Finished: verify_data = HMAC(finished_key, Transcript-Hash) -``` - -### 3.4 关键 AEAD / Key Schedule 不变量 -- **TLS 1.3 nonce**: `iv[0..11] XOR (00000000 || seq_be64)`,**不重复使用**。 -- **TLS 1.3 AAD**: `type || 0x0303 || (plaintext_len+16)`。 -- **TLS 1.2 AAD**: `seq(8) || type || version || (明文长度)`,explicit nonce(8) 写在记录 body 开头,nonce = `fixed_iv(4) || explicit_nonce(8)`(RFC 5288 §3 GCMNonce),fixed_iv=key_material 中 write_IV(4 字节)。 -- **Transcript**: 每次发送/收到握手消息后,**连同 4 字节握手头**一并 update。 -- **HRR 回退**:调用 `paze_tls_tr_reset_to_synthetic()` 重置 transcript 为 `MessageHash(Hash(CH1))`,随后 HRR、CH2、SH… 追加其 后。 -- **PSS/RSA-PKCS1 签名**:由 `paze_rsa_pss_sign` / `paze_rsa_pkcs1_sign` 直接产出 signature 字节流。 -- **证书链验证**:调用 `paze_x509_verify_chain(peer_chain[], chain_len, anchors[], anchor_count, now)`;之后 `paze_x509_match_host(leaf, sni)`。 - ---- - -## 四、SSH 模块设计 (PazeSSH, ≤25,000 行) - -### 4.1 协议栈(对应源文件) -``` -┌──────────────────────────────────────────┐ -│ apps/ssh / apps/sshd (CLI) │ -├──────────────────────────────────────────┤ -│ src/ssh/ssh_client.c / ssh_server.c │ ← 入口(connect/listen→accept→loop) -│ └── ssh_auth.c │ ← 用户认证(pubkey/password/hostbased) -│ └── ssh_channel.c │ ← 通道建立(session/shell/exec/pty) -│ └── ssh_kex.c │ ← 密钥交换 (curve25519-sha256@libssh.org,ecdh-sha2-nistp256) -│ └── ssh_transport.c │ ← 二进制包加密/解密、重加密、Mac/压缩、序号 -│ └── ssh_packet.c │ ← 打包:uint32(长度) + padl(1) + payload + padding + mac -│ └── ssh_keys.c │ ← 主机键 RSA/Ed25519/ECDSA P256 加载+签名+验签 -└──────────────────────────────────────────┘ -``` - -### 4.2 密码学兼容性 -- **kex methods**:`curve25519-sha256@libssh.org`,`ecdh-sha2-nistp256`,`diffie-hellman-group14-sha256`(仅兼容,不建议启用) -- **host key algorithms**:`ssh-ed25519`,`ecdsa-sha2-nistp256`,`rsa-sha2-512`,`rsa-sha2-256` -- **encryption**:`chacha20-poly1305@openssh.com`,`aes128-gcm@openssh.com`,`aes256-gcm@openssh.com` -- **MAC**(非 AEAD):`hmac-sha2-256` -- **key format**:`ssh-rsa / ssh-ed25519 / ecdsa-sha2-nistp256` 公钥格式 + PEM 私钥 -- **packet size**:packet.c 中 `uint32 block_size` 依 cipher 动态设置 - -### 4.3 行数分配建议 -| 文件 | 预算 | 核心功能 | 状态 | -|---|---|---|---| -| ssh_packet.c | 600 | uint32 打包解包、padding、AEAD 加密/解密、序列号递增、mac 校验、Factory 函数 | ✅ | -| ssh_transport.c | 1,200 | 版本交换、KEXINIT 协商、算法名列表解析 | ✅ | -| ssh_keys.c | 1,500 | 主机键加载(PEM)、公钥 blob 格式、签名 verify/sign (Ed25519/ECDSA/RSA) | ✅ | -| ssh_kex.c | 1,800 | X25519/P-256 ECDH 交换、密钥派生(ChaCha20-Poly1305/256 bit keys)、NEWKEYS | ✅ | -| ssh_auth.c | 2,000 | USERAUTH_REQUEST/FAILURE/SUCCESS; password authentication | ✅ | -| ssh_channel.c | 2,500 | CHANNEL_OPEN/CONFIRM/DATA/REQUEST/CLOSE; session/shell/exec/pty-req | ✅ | -| ssh_client.c | 1,000 | TCP connect → transport_loop → auth → channel shell/exec | ✅ | -| ssh_server.c | 1,500 | listen → accept → transport → auth → channel pty+shell (echo 测试) | ✅ | -| **小计** | **≈ 12,100** | **全部 8 模块完成** | ✅ | - ---- - -## 五、密码学原语清单(已完整实现,勿重复) - -``` -——哈希 - paze_sha1, paze_sha256, paze_sha384, paze_sha512, paze_md5 - 通用描述符: paze_hash_sha256() 等 (含 digest_len/block_len/ctx_size/init/update/final) - -——密钥派生 - paze_hmac(alg, key, msg) / 流式 hmac_init/update/final - paze_hkdf_extract / expand / paze_hkdf - paze_pbkdf2 - -——对称 AEAD - paze_aes128/256_init + aesgcm_encrypt/decrypt (12 字节 nonce, 16 字节 tag) - paze_chacha20poly1305_encrypt/decrypt (RFC 8439, 12 字节 nonce, 32 字节 key) - AES ECB/CBC/CTR 模式(低层次,仅特殊场景用) - -——椭圆曲线 - curve25519: paze_curve25519_scalar_mult(pub, priv, basepoint_or_pubkey) - Ed25519: paze_ed25519_publickey / sign / verify (RFC 8032, SHA-512) - P-256: paze_p256_scalar_mult(R, k, P) / from_affine / to_affine / paze_ecdsa_sign / verify - -——大整数 / RSA - paze_bn_t (最多 PAZE_BN_MAX_LIMBS 个 32-bit limb) 加减乘除 模 Montgomery 模幂 modexp - RSA: paze_rsa_pub_from_der / priv_from_pem? - paze_rsa_pkcs1_encrypt/decrypt/sign/verify - paze_rsa_pss_sign/verify - paze_rsa_gen(&priv, bits) —— 密钥生成(Miller-Rabin 素性检测) - -——ASN.1 + X.509 - paze_asn1_reader_t: enter_seq / read_tl / at_end / 基本类型 ASN1_* tag 常量 - paze_x509_parse_der / parse_pem / free - paze_x509_verify_signature(cert, issuer) - paze_x509_verify_chain(chain[], chain_len, anchors[], anchor_count, now_unix_seconds) - paze_x509_check_validity / match_host(host) ← SAN dNSName 支持通配?不支持(严格) - paze_x509_make_self_signed(out, outlen, signer, CN, not_before, not_after) ← 测试用 - -——编码 - paze_base64_encode/decode, paze_hex_encode/decode, paze_pem_encode/decode -``` - ---- - -## 六、跨平台 Socket + BIO 约定 - -### 6.1 apps 层的 BIO 构造(Windows + POSIX 同文件,用 `#if _WIN32`) -```c -#ifdef _WIN32 -#include -typedef SOCKET sock_t; -#define INVALID INVALID_SOCKET -#else -#include -#include -typedef int sock_t; -#define INVALID (-1) -#endif - -static int sock_read(void *ctx, uint8_t *buf, size_t n) { - sock_t s = *(sock_t*)ctx; - return (int)recv(s, (char*)buf, (int)n, 0); // WIN/UNIX 同签名 -} -static int sock_write(void *ctx, const uint8_t *buf, size_t n) { - sock_t s = *(sock_t*)ctx; - return (int)send(s, (char*)buf, (int)n, 0); -} - -paze_tls_bio_t bio = { &sock, sock_read, sock_write }; -paze_tls_set_bio(tls, bio); -``` - -### 6.2 错误码统一 -- `PAZE_ERR_IO`(-8): 底层 BIO 出错,**不 retry**。 -- `PAZE_ERR_VERIFY`(-5): 验签失败 / record MAC 错误 / 证书不可信。 -- `PAZE_ERR_PROTOCOL`(-10): 协议消息格式错误。 -- 其他负数按 [paze_types.h](file:///F:/TLS-SSH/include/paze/paze_types.h)。 - ---- - -## 七、构建系统 - -顶层 [CMakeLists.txt](file:///F:/TLS-SSH/CMakeLists.txt) 已规划静态库: -``` -paze_crypto ← src/crypto + src/util -paze_ssl ← src/ssl/*.c (链接 paze_crypto) -paze_ssh ← src/ssh/*.c (链接 paze_crypto) -CLI: pazessl, paze-ssh, paze-sshd -TESTS: paze_kat (tests/kat.c) -``` - -### 构建命令 (Windows MinGW/GCC 或 MinGW-w64 / Unix GCC / Clang) -```bash -# Linux / macOS -mkdir -p build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Release -make -j$(nproc) && ctest - -# Windows (MSVC) -mkdir build && cd build -cmake .. -cmake --build . --config Release && ctest -C Release -``` - ---- - -## 八、CLI 工具 (apps/) - -### pazessl (平替 `openssl s_client / s_server / s_time / x509 / verify`) -``` -pazessl s_client -connect [-CAfile roots.pem] [-servername SNI] [-alpn h2,http1.1] -pazessl s_server -accept -cert cert.pem -key key.pem [-CAfile roots.pem] -pazessl verify -CAfile roots.pem cert.pem -pazessl x509 -in cert.pem [-text] [-noout] -pazessl s_time -connect [-seconds 10] -pazessl genrsa -out key.pem [2048|4096] -pazessl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -subj /CN=test -``` - -### paze-ssh (平替 `ssh`) -``` -paze-ssh [-p port] [-i key.pem] [-l user] host [command] -``` - -### paze-sshd (平替 `sshd`) -``` -paze-sshd -p 22 -h host_key_ed25519.pem [-h host_key_rsa.pem] \ - -f authorized_keys_dir # 目录下 $user/.ssh/authorized_keys -``` - ---- - -## 九、统一 KAT 测试 (tests/kat.c) - -`paze_kat` 二进制须全部通过,作为 CI 门禁。 - -```c -int main(void) { - int ok = 1; - ok &= test_sha2_vectors(); // NIST 向量 - ok &= test_aes_gcm_vectors(); // NIST SP 800-38D - ok &= test_chacha20_poly(); // RFC 8439 - ok &= test_rsa_2048_roundtrip(); - ok &= test_ecdsa_p256_kat(); - ok &= test_ed25519_rfc8032(); - ok &= test_x25519_rfc7748(); - ok &= test_hkdf_rfc5869(); - ok &= test_pbkdf2_rfc6070(); - ok &= test_x509_self_rsa(); - ok &= test_x509_self_ecdsa(); - ok &= test_tls13_self_handshake(); // ← 新: 内建回环测试 - ok &= test_ssh_kex_loopback(); // ← 新 - printf("KAT %s\n", ok ? "PASS" : "FAIL"); - return ok ? 0 : 1; -} -``` - ---- - -## 十、遗留 TO-DO(完成顺序) - -按依赖顺序:**TLS extensions → handshake13 → conn → tests → SSH 全部 → CLI → verify** - -1. ✅ 密码学原语 + X.509 -2. ✅ tls.h + tls_internal.h -3. ✅ tls_bio / tls_record / tls_transcript / tls_keys -4. ✅ tls_extensions (write + parse) -5. ✅ tls_handshake13 (客户端 + 服务端状态机,无 PSK/0-RTT/HRR 实际数据) -6. ✅ tls_client / tls_server / tls_conn (握手入口、证书验证、连接 API、应用读写) -7. ✅ SSH 全部 (ssh_packet/transport/kex/keys/auth/channel/client/server) -8. ✅ apps/ (pazessl s_client/s_server/verify/genrsa/req/x509 + paze-ssh + paze-sshd) -9. ✅ tests/kat.c (SHA-256/X25519/Ed25519/RSA/X.509/TLS/SSH 测试) -10. ✅ 全量构建:所有 3 个静态库 + 4 个 CLI 程序 + 12 个测试程序通过编译 - -### 遗留问题 (pre-existing, 非本次引入) - -11. ✅ tls_handshake12 (TLS 1.2 回退,ECDHE-RSA/ECDSA/RSA AES-GCM + CHACHA20-POLY1305;OpenSSL s_client/s_server 双向 1.2 互操作通过) -12. 🔶 X.509 self-signed 证书解析 — RSA 部分已修复(X.509 SPKI 外层 SEQUENCE 缺失问题), ECDSA 仍失败(p256_from_affine 曲线点验证不通过) -13. ⬜ X25519 ECDH 共享密钥不一致 (RFC 7748 标准测试向量不通过) -14. ✅ SSH 密钥解析 — paze_rsa_priv_from_pem / paze_ecdsa_priv_from_pem 已实现, tls_conn 私钥加载已更新 -15. ⬜ TLS 1.3 密钥派生重放 (服务端收到 ClientHello 时若 cipher=SHA-384 需要重放 CH 到新 transcript,SHA-256 已覆盖) -16. ⬜ 最终验证:全量 `cloc`/`wc -l` 统计 + 所有 KAT 通过 + TLS 1.3 对公网 TLS 1.3 服务器握手测试 - -### 2026-08-11 新增功能 - -17. ✅ paze_x509_build_spki 修复:AlgorithmIdentifier + BIT STRING 现包裹在外层 SEQUENCE 中 -18. ✅ paze_rsa_priv_from_pem / paze_rsa_pub_from_pem — RSA 密钥 PEM 加载 -19. ✅ paze_ecdsa_priv_from_pem / paze_ecdsa_pub_from_pem — ECDSA 密钥 PEM 加载 (RFC 5915) -20. ✅ 多证书链 PEM 加载 (build_cert_chain_from_pem 支持多段) -21. ✅ 多信任锚 PEM 加载 (paze_tls_config_set_trust_roots 支持多根证书) - ---- - -## 十一、安全编码原则 - -1. **常量时间**: - - 密钥 / IV / padding 比较必须走 `paze_memzero` 或 `paze_ct_equal`。 - - RSA PKCS1-v1_5 解密必须用累积 diff 而非早退(已在 rsa.c 中实现)。 -2. **缓冲区边界**:每个 `memcpy` 前必须显式校验长度,不得假设 `rlen ≤ MAX_RECORD`。 -3. **序列号溢出**:若 `seq == UINT64_MAX - 1`(实际上不会到),需发 KeyUpdate 或关连接。 -4. **错误信息**:不得泄漏内部状态;对外仅返回状态码,调试信息只写入 `paze_log(DEBUG)`。 -5. **内存清零**:所有密钥材料(handshake_secret/master_secret/private keys)使用完毕后 `paze_memzero`。 -6. **RSA PKCS1 长度校验**:验签前严格重构 expected EM,整段常量时间比较(rsa.c 已如此实现)。 - ---- - -## 十二、引用 - -- TLS 1.3 — [RFC 8446](https://www.rfc-editor.org/rfc/rfc8446) -- TLS 1.2 — [RFC 5246](https://www.rfc-editor.org/rfc/rfc5246) -- AES-GCM — [NIST SP 800-38D](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf) -- ChaCha20-Poly1305 — [RFC 8439](https://www.rfc-editor.org/rfc/rfc8439) -- HKDF — [RFC 5869](https://www.rfc-editor.org/rfc/rfc5869) -- X.509 & PKIX — [RFC 5280](https://www.rfc-editor.org/rfc/rfc5280) -- SSH 协议族 — [RFC 4250~4256](https://www.rfc-editor.org/rfc/rfc4250) - -所有代码一定要能移植到裸机上! - ---- - -## 十三、构建命令(已验证) - -```bash -# Windows (Clang/MinGW via CMake): -mkdir build && cd build -cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_C_FLAGS="-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_WINSOCK_DEPRECATED_NO_WARNINGS" -make -j$(nproc) - -# 运行测试 -./paze_kat.exe -ctest - -# Linux / macOS -mkdir build && cd build -cmake .. -DCMAKE_BUILD_TYPE=Release -make -j$(nproc) && ctest -``` - -**注意**: Windows 上需要 `-D_CRT_SECURE_NO_WARNINGS` 等宏以抑制 MSVC 运行时对 `strcpy`/`strdup`/`gethostbyname` 的废弃警告。 -- `src/crypto/fe25519.c` 已在 CMakeLists.txt 中(2026-08-11 补充,原遗漏) -- `strcasecmp` 在 Windows 上为 `_stricmp`,已在 `tests/bn_check.c` 添加 compat 宏 - ---- - -## 十四、现有代码行数统计 (2026-08-12) - -全量统计 `src/ + include/ + apps/ + tests/` 下 `*.c/*.h`(93 + 18 = 111 个文件): - -| 目录 | 行数 | 说明 | -|---|---:|---| -| src/crypto | 4,938 | 哈希/AES/ChaCha20/曲线/RSA/X.509/ASN.1 | -| src/ssh | 3,779 | packet/transport/kex/keys/auth/channel/client/server/scp/agent | -| src/ssl | 2,328 | TLS 1.3 握手/记录/密钥派生 | -| include/paze | 1,453 | 公共头 | -| src/util | 77 | mem/error/log/version | -| include | 42 | 顶层头 | -| apps | 1,839 | pssh(8 子命令) + pazessl | -| tests | 1,688 | KAT 测试 | -| **合计** | **16,144** | **预算 ≤ 100,000,剩余 83,856** | - ---- - -## 十五、pssh 与 OpenSSH 对比 - -### 命令对应 - -| OpenSSH | pssh | 说明 | -|---|---|---| -| `ssh` | `pssh`(默认)/ `pssh ssh` | 远程 shell / 执行命令 | -| `scp` | `pssh scp` | 文件传输(SFTP 子系统优先,兼容 OpenSSH 9.x;回退 legacy SCP) | -| `sftp` | `pssh sshd` 子系统 | 服务端 SFTP 子系统 v3,与 OpenSSH sftp/scp -s 互操作 | -| `ssh-keygen` | `pssh keygen` | 密钥生成(ed25519/rsa/ecdsa) | -| `ssh-keyscan` | `pssh keyscan` | 主机密钥扫描(known_hosts 格式) | -| `ssh-copy-id` | `pssh copy-id` | 安装公钥到 authorized_keys | -| `ssh-agent` | `pssh agent` | agent 守护进程 | -| `ssh-add` | `pssh add` / `pssh ssha` | agent 密钥管理 | -| `sshd` | `pssh sshd` | SSH 服务器(多用户库 + 权限控制) | - -### 认证与加密算法 - -| 项目 | OpenSSH | pssh | -|---|---|---| -| 密码认证 | ✓ | ✓(交互 `*` 掩码 / `-pwd`) | -| publickey 认证 | ✓ | ✓(`-i` 私钥 + agent 签名) | -| agent 代理认证 | ✓ | ✓(Windows 命名管道 `\\.\pipe\openssh-ssh-agent`) | -| 密钥交换 | curve25519/ECDH/DH/sntrup761 等 | curve25519-sha256@libssh.org(X25519) | -| 主机密钥 | ed25519/rsa/ecdsa/dsa | ed25519/rsa/ecdsa | -| 加密算法 | chacha20-poly1305、aes*-ctr、aes*-gcm 等 | chacha20-poly1305@openssh.com、aes128/256-gcm@openssh.com | -| 私钥格式 | openssh-key-v1(可 passphrase 加密) | openssh-key-v1(无加密) | -| 私钥指纹 | SHA256/MD5 | SHA256 | - -### 功能对比 - -| 功能 | OpenSSH | pssh | -|---|---|---| -| 交互 shell + 方向键历史 | ✓ | ✓(UTF-8 中文正常) | -| 远程执行命令 | ✓ | ✓ | -| known_hosts 校验 | ✓(yes/no/accept-new) | ✓(同) | -| SCP 协议 | legacy + SFTP 模式(9.x 默认) | 仅 legacy SCP | -| 递归目录传输 `-r` | ✓ | ✓ | -| 保留时间戳 `-p` | ✓ | ✓ | -| 端口转发 `-L/-R` | ✓ | ✓(实测 HTTP 200) | -| 动态转发/SOCKS5 `-D` | ✓ | ✓(实测代理 HTTP 200) | -| 代理跳转 `-J` | ✓ | ✓(多级跳转 + 退出码透传) | -| config 文件 | ✓(`~/.ssh/config`) | ✓(`~/.pssh/config.conf`) | -| 服务器端 | 完整(用户库/子系统/authorized_keys) | 多用户库 + 分级权限(guest/user/admin) + authorized_keys 公钥认证 | -| 服务器端 exec/shell | ✓ | ✓(真正执行本地命令,stdout/stderr/exit-status 回传) | -| 服务器端 direct-tcpip | ✓ | ✓(出站连接 + 双向中继,需 admin 权限) | - -### 主要差距(后续建议) - -1. **私钥 passphrase 加密**(openssh-key-v1 AES 加密段) \ No newline at end of file +## 硬性规则 + +1. **目标平台 Los4**:不实现 Windows/Linux/macOS 编译目标(当前平台仅作构建宿主)。 +2. **零第三方依赖**:仅 ISO C17 + libc;不用 OpenSSL/mbedTLS/GMP;熵来自系统调用。 +3. **结构分层**:`crypto 原语 → ssl/ssh 协议 → apps 命令`,协议层不直接依赖应用层。 +4. **常量时间**:密码学核心路径禁止密钥相关分支(用 `paze_ct_equal` 等)。 +5. **编码风格**:纯 C、紧凑直接、注释用中文、不做多余抽象层、单文件状态机。 +6. **构建**:`.\build.ps1`(gcc,C17),产物进 `bin/`,中间件留 `build/`。 +7. **测试**:`build/run_tls_test.ps1` 自测、`build/run_openssl_interop.ps1` 互操作;改动后必须跑通自测。 +8. **Git**:中文提交信息;不主动推送,用户要求才推(parlz=gitea、origin=GitHub);无 push 权限/网络问题时如实报告。 +9. **文档**:改功能同步更新 STATUS.md;不建多余 .md。 + +## 待办(按优先级) + +### 1. 0-RTT / HRR — TLS 1.3 数据路径(✅ 已完成 2026-08-13) +- 服务端:CH 无 key_share → 发 HRR(random=魔数 `CF21AD74...F84A4F`)→ 等 CH2;`sent_hrr` 标记 +- 客户端:识别 HRR 魔数 → `paze_tls_tr_reset_to_synthetic` → 生成 CH2(仅选定组 share)→ 放弃 0-RTT +- 0-RTT:客户端 CH 带 early_data 扩展 + 发 early 数据(early_write);服务端解密(early_read);EndOfEarlyData 用 client_early_traffic_secret +- 验证:`pazessl s_client -early -sess_in ...` / `s_server -early` 端到端通过(同进程会话复用);`-key_share P-256` 触发 HRR 通过 + +### 2. TLS secp384r1 +实现 P-384 点运算(bignum 已有),加曲线 + 套件,中等偏繁。 + +### 3. SSH ProxyJump +客户端连接管理(经跳板机转发),中等。 diff --git a/STATUS.md b/STATUS.md index f0b8ef5..47bc904 100644 --- a/STATUS.md +++ b/STATUS.md @@ -30,7 +30,8 @@ - ✅ 证书链与信任锚加载、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 数据路径 +- ✅ **0-RTT 数据路径**(RFC 8446 §4.2.10/§4.5/§8):early_data 扩展、client_early_traffic_secret 密钥派生、握手前早数据收发、EndOfEarlyData(early key 加密且计入 transcript);`pazessl s_client/s_server -early` 端到端验证(早数据接收 + early accepted) +- ✅ **HRR(HelloRetryRequest)**(RFC 8446 §4.1.3/§4.1.4):服务端 key_share 不匹配时发 HRR(随机数=固定魔数)、transcript 合成 MessageHash 重放、客户端 CH2(仅选定组 share)响应、HRR 后放弃 0-RTT;`-key_share` 选项触发,`hrr=YES` 互操作验证通过 ## PazeSSH — SSH-2(✅ 完成) @@ -69,4 +70,4 @@ ## 遗留项(已知) -- 0-RTT 数据路径与 HRR(HelloRetryRequest)未实现 +- 无(TLS 1.2 / 1.3 与 SSH-2 全部既定功能完成) diff --git a/apps/pazessl/main.c b/apps/pazessl/main.c index 3605095..31cd78d 100644 --- a/apps/pazessl/main.c +++ b/apps/pazessl/main.c @@ -9,6 +9,7 @@ * x509 - 打印证书 */ #include "paze/tls.h" +#include "paze/log.h" #include "paze/encoding.h" #include "paze/rsa.h" #include "paze/x509.h" @@ -70,8 +71,9 @@ static int usage(void) { fprintf(stderr, "PazeSSL v0.1 (libc-only TLS 1.3 + 1.2)\n" "Usage:\n" - " pazessl s_client -connect host:port [-CAfile roots.pem] [-servername SNI] [-tls1_2]\n" - " pazessl s_server -accept port -cert cert.pem -key key.pem [-tls1_2]\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]\n" + " pazessl s_server -accept port -cert cert.pem -key key.pem [-tls1_2] [-reuse N] [-early]\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" @@ -124,7 +126,8 @@ int main(int argc, char **argv) { const char *sni = NULL; const char *sess_in = NULL, *sess_out = NULL; const char *msg = NULL; - int only_12 = 0; + int only_12 = 0, do_early = 0; + const char *key_share = NULL; for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "-connect") == 0 && i + 1 < argc) { char buf[256]; snprintf(buf, sizeof(buf), "%s", argv[++i]); @@ -143,12 +146,26 @@ int main(int argc, char **argv) { msg = 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], "-key_share") == 0 && i + 1 < argc) { + key_share = argv[++i]; + } else if (strcmp(argv[i], "-v") == 0) { + paze_log_set_level(PAZE_LOG_DEBUG); } } if (!host) { fprintf(stderr, "need -connect host:port\n"); return 1; } paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_CLIENT); if (only_12) paze_tls_config_set_versions(cfg, 0, 1); + if (do_early) paze_tls_config_enable_early_data(cfg, 1); + if (key_share) { + uint16_t g = 0; + if (strcmp(key_share, "X25519") == 0) g = PAZE_TLS_GROUP_X25519; + else if (strcmp(key_share, "P-256") == 0) g = PAZE_TLS_GROUP_SECP256R1; + else { fprintf(stderr, "bad -key_share (X25519|P-256)\n"); return 1; } + if (paze_tls_config_set_kex_group(cfg, g) != PAZE_OK) return 1; + } if (sni) paze_tls_config_set_sni(cfg, sni); const char *alpn_default[] = { "http/1.1" }; paze_tls_config_set_alpn(cfg, alpn_default, 1); @@ -173,6 +190,13 @@ int main(int argc, char **argv) { paze_tls_bio_t bio = { &sock, sock_read, sock_write }; paze_tls_set_bio(tls, bio); + /* 0-RTT:握手前发送早数据(需 -sess_in 且 -early) */ + if (do_early && msg) { + int n = paze_tls_early_write(tls, (const uint8_t *)msg, strlen(msg)); + if (n < 0) { fprintf(stderr, "early write failed\n"); return 1; } + fprintf(stderr, "0-RTT sent %d bytes\n", n); + } + paze_status_t st; for (int retry = 0; retry < 100; retry++) { st = paze_tls_handshake(tls); @@ -183,10 +207,12 @@ int main(int argc, char **argv) { paze_tls_free(tls); closesocket(sock); paze_tls_config_free(cfg); return 1; } - fprintf(stderr, "Connected (TLS %04x, cipher %04x, resumed %s)\n", + fprintf(stderr, "Connected (TLS %04x, cipher %04x, resumed %s%s%s)\n", (unsigned)paze_tls_get_version(tls), (unsigned)paze_tls_get_cipher_suite(tls), - paze_tls_session_resumed(tls) ? "YES" : "no"); + paze_tls_session_resumed(tls) ? "YES" : "no", + paze_tls_hrr_occurred(tls) ? ", hrr=YES" : "", + (do_early && paze_tls_early_data_accepted(tls)) ? ", early accepted" : ""); if (sess_out) { paze_tls_session_t ss; if (paze_tls_get_session(tls, &ss) == PAZE_OK && ss.valid) { @@ -199,7 +225,8 @@ int main(int argc, char **argv) { uint8_t buf[16384]; if (msg) { - paze_tls_write(tls, (const uint8_t *)msg, strlen(msg)); + /* 0-RTT 已提前发送,否则现在发 */ + if (!do_early) paze_tls_write(tls, (const uint8_t *)msg, strlen(msg)); int n = paze_tls_read(tls, buf, sizeof(buf)); if (n > 0) { fwrite(buf, 1, (size_t)n, stdout); fprintf(stderr, "\n"); } } else { @@ -347,7 +374,7 @@ int main(int argc, char **argv) { if (strcmp(cmd, "s_server") == 0) { int port = 4433; const char *certf = NULL; const char *keyf = NULL; - int only_12 = 0; + int only_12 = 0, do_early = 0; int reuse = 1; for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "-accept") == 0 && i + 1 < argc) port = atoi(argv[++i]); @@ -355,6 +382,8 @@ int main(int argc, char **argv) { else if (strcmp(argv[i], "-key") == 0 && i + 1 < argc) keyf = argv[++i]; 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], "-v") == 0) paze_log_set_level(PAZE_LOG_DEBUG); } if (!certf || !keyf) { fprintf(stderr, "s_server needs -cert and -key\n"); @@ -370,6 +399,7 @@ int main(int argc, char **argv) { /* Setup TLS config */ paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_SERVER); if (only_12) paze_tls_config_set_versions(cfg, 0, 1); + 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); @@ -413,10 +443,11 @@ int main(int argc, char **argv) { if (st != PAZE_OK) { fprintf(stderr, "TLS handshake failed: %s\n", paze_tls_last_error(tls)); } else { - fprintf(stderr, "TLS handshake OK (version %04x, cipher %04x, resumed %s)\n", + fprintf(stderr, "TLS handshake OK (version %04x, cipher %04x, resumed %s%s)\n", (unsigned)paze_tls_get_version(tls), (unsigned)paze_tls_get_cipher_suite(tls), - paze_tls_session_resumed(tls) ? "YES" : "no"); + paze_tls_session_resumed(tls) ? "YES" : "no", + paze_tls_early_data_len(tls) > 0 ? ", early data received" : ""); /* 跨连接共享 session ticket key,使后续连接可恢复会话 */ uint8_t tk[32]; int tk_ready = 0; paze_tls_get_session_ticket_key(tls, tk, &tk_ready); diff --git a/include/paze/tls.h b/include/paze/tls.h index dabba7a..e0acbb9 100644 --- a/include/paze/tls.h +++ b/include/paze/tls.h @@ -195,6 +195,13 @@ void paze_tls_config_free(paze_tls_config_t *cfg); /* 设置 SNI 主机名(client) */ paze_status_t paze_tls_config_set_sni(paze_tls_config_t *cfg, const char *host); +/* 启用/禁用 0-RTT(RFC 8446 §4.2.10): + * 客户端:enable=1 时,配合有效会话,CH 携带 early_data 扩展并派生 0-RTT 密钥, + * 应用可在 paze_tls_handshake() 之前用 paze_tls_early_write() 发送 0-RTT 数据; + * 服务端:enable=1 时接受 0-RTT(EE 回 early_data 扩展,early_read 解密早数据)。 + * 默认禁用。 */ +paze_status_t paze_tls_config_enable_early_data(paze_tls_config_t *cfg, int enable); + /* 设置 ALPN 协议列表(以 NULL 结尾的字符串数组,n 项) */ paze_status_t paze_tls_config_set_alpn(paze_tls_config_t *cfg, const char *const *alpn, int n); @@ -216,6 +223,10 @@ paze_status_t paze_tls_config_set_verify(paze_tls_config_t *cfg, paze_status_t paze_tls_config_set_versions(paze_tls_config_t *cfg, int enable_1_3, int enable_1_2); +/* 客户端:限制 ClientHello key_share 只携带指定组(X25519=0x001D / P-256=0x0017)。 + * 其余组仍在 supported_groups 中,可用于触发服务端 HelloRetryRequest。 */ +paze_status_t paze_tls_config_set_kex_group(paze_tls_config_t *cfg, uint16_t group); + /* ============================================================ * 连接对象 * ============================================================ */ @@ -248,10 +259,23 @@ paze_status_t paze_tls_handshake(paze_tls_t *tls); int paze_tls_is_handshake_done(const paze_tls_t *tls); /* 应用层数据读写。read 返回读取字节数(>0);0=对端关闭;负数=错误。 - PAZE_ERR_IO(非阻塞无数据)时上层应等待可读后重试。 */ + PAZE_ERR_IO(非阻塞无数据)时上层应等待可读后重试。 + 服务端:若本次握手收到 0-RTT 数据,read 会优先返回这些缓冲的早数据。 */ int paze_tls_read(paze_tls_t *tls, uint8_t *buf, size_t len); int paze_tls_write(paze_tls_t *tls, const uint8_t *buf, size_t len); +/* 0-RTT (RFC 8446 §4.5): + * 客户端在 paze_tls_handshake() 之前发送 0-RTT 数据(必须先启用 early_data + * 且配置了有效会话)。返回写入字节数(>0);未启用/无会话/已发生 HRR 等 + * 不可用时返回 PAZE_ERR_NOTSUP,参数错误返回 PAZE_ERR_INVALID。 */ +int paze_tls_early_write(paze_tls_t *tls, const uint8_t *buf, size_t len); +/* 0-RTT 是否被接受(客户端:EE 含 early_data;服务端:已回 early_data 扩展) */ +int paze_tls_early_data_accepted(const paze_tls_t *tls); +/* 服务端收到的 0-RTT 数据字节数(客户端恒为 0) */ +size_t paze_tls_early_data_len(const paze_tls_t *tls); +/* 本次握手是否发生 HelloRetryRequest(客户端视角) */ +int paze_tls_hrr_occurred(const paze_tls_t *tls); + /* 优雅关闭:发送 close_notify */ paze_status_t paze_tls_close(paze_tls_t *tls); diff --git a/src/ssl/tls_conn.c b/src/ssl/tls_conn.c index 3e33145..c1ad355 100644 --- a/src/ssl/tls_conn.c +++ b/src/ssl/tls_conn.c @@ -59,6 +59,22 @@ paze_status_t paze_tls_config_set_alpn(paze_tls_config_t *cfg, const char *const return PAZE_OK; } +paze_status_t paze_tls_config_enable_early_data(paze_tls_config_t *cfg, int enable) { + if (!cfg) return PAZE_ERR_INVALID; + /* 0-RTT 仅对 PSK 会话恢复有意义;客户端开启即可主动发, + * 服务端开启才会在 EE 中回 early_data 扩展并接受早数据。 */ + cfg->enable_early = enable ? 1 : 0; + return PAZE_OK; +} + +paze_status_t paze_tls_config_set_kex_group(paze_tls_config_t *cfg, uint16_t group) { + if (!cfg) return PAZE_ERR_INVALID; + if (group != PAZE_TLS_GROUP_X25519 && group != PAZE_TLS_GROUP_SECP256R1) + return PAZE_ERR_INVALID; + cfg->kex_group = group; + return PAZE_OK; +} + /* 由证书 PEM 文本构造证书链(CertificateEntry 序列)。 * 支持多张证书串联:依次拼接 [3字节长度前缀 + DER]。 * 返回 malloc 的缓冲区,*outlen 为长度。 */ @@ -314,6 +330,8 @@ void paze_tls_free(paze_tls_t *tls) { paze_tls_config_free(tls->cfg_owned); free(tls->ch1); free(tls->sh1); + free(tls->ch2); + free(tls->hrr_raw); paze_memzero(tls, sizeof(*tls)); free(tls); } @@ -361,6 +379,13 @@ paze_status_t paze_tls_handshake(paze_tls_t *tls) { * ============================================================ */ int paze_tls_read(paze_tls_t *tls, uint8_t *buf, size_t len) { if (!tls || !buf || len == 0) return PAZE_ERR_INVALID; + /* 先消费握手期间缓冲的 0-RTT 早数据(服务端视角) */ + if (tls->early_off < tls->early_len) { + size_t n = len < tls->early_len - tls->early_off ? len : tls->early_len - tls->early_off; + memcpy(buf, tls->early_buf + tls->early_off, n); + tls->early_off += n; + return (int)n; + } /* 先消费 app_buf 中已有的未读明文 */ if (tls->app_off < tls->app_len) { size_t n = len < tls->app_len - tls->app_off ? len : tls->app_len - tls->app_off; @@ -435,6 +460,51 @@ int paze_tls_is_handshake_done(const paze_tls_t *tls) { return tls ? tls->handshake_done : 0; } +/* 0-RTT:在握手完成前发送早数据(仅客户端,且已开启 early_data)。 + * 首次调用前会发送 ClientHello(发送完 CH 即持有 early_write 密钥)。 + * 若随后发生 HRR 或服务端拒绝,早数据会被静默丢弃(不返回错误)。 */ +int paze_tls_early_write(paze_tls_t *tls, const uint8_t *buf, size_t len) { + if (!tls || (!buf && len) || tls->role != PAZE_TLS_CLIENT) + return PAZE_ERR_INVALID; + if (tls->handshake_done || !tls->cfg_owned->enable_early) + return PAZE_ERR_INVALID; + if (!tls->early_write_valid) { + /* 发送 ClientHello(幂等:首次发送后置 early_write_valid, + * 并推进 hs_state 使握手入口不再重发) */ + if (tls->hs_state == PAZE_HS_INIT) { + if (paze_tls13_client_send_ch(tls) != PAZE_OK) + return -1; + tls->hs_state = PAZE_HS_CL_WAIT_SH; + } + } + if (!tls->early_write_valid) return -1; /* 非 PSK 会话,无法发送 */ + tls->early_sent = 1; + size_t done = 0; + while (done < len) { + size_t chunk = len - done; + if (chunk > PAZE_TLS_MAX_RECORD) chunk = PAZE_TLS_MAX_RECORD; + paze_status_t st = paze_tls_record_send(tls, PAZE_TLS_CT_APPLICATION_DATA, + buf + done, chunk); + if (st != PAZE_OK) return -1; + done += chunk; + } + return (int)done; +} + +/* 服务端 EE 是否含 early_data(客户端视角:0-RTT 是否被接受) */ +int paze_tls_early_data_accepted(const paze_tls_t *tls) { + if (!tls || tls->role != PAZE_TLS_CLIENT) return 0; + return tls->early_accepted; +} + +int paze_tls_hrr_occurred(const paze_tls_t *tls) { + return tls ? tls->had_hrr : 0; +} + +size_t paze_tls_early_data_len(const paze_tls_t *tls) { + return tls ? tls->early_len : 0; +} + paze_status_t paze_tls_close(paze_tls_t *tls) { if (!tls) return PAZE_ERR_INVALID; if (!tls->close_sent && tls->handshake_done) { diff --git a/src/ssl/tls_extensions.c b/src/ssl/tls_extensions.c index 3d736f3..481a41c 100644 --- a/src/ssl/tls_extensions.c +++ b/src/ssl/tls_extensions.c @@ -132,8 +132,8 @@ size_t paze_tls_ext_write_key_share_client(uint8_t *buf, const paze_tls_kex_t *k /* HRR 后(only_group != 0):只发指定组的 share(RFC 8446 §4.1.4)。 * 否则按 kind 全量发送(首次 CH:尽可能避免 HRR 往返)。 */ if (only_group == PAZE_TLS_GROUP_X25519 || - (only_group == 0 && (kex->kind == PAZE_KEX_X25519 || kex->kind == PAZE_KEX_P256))) { - /* X25519 share */ + (only_group == 0 && kex->kind == PAZE_KEX_X25519)) { + /* X25519 share(kind==P256 时该公钥未生成,不得写入) */ paze_store16_be(p, PAZE_TLS_GROUP_X25519); p += 2; paze_store16_be(p, 32); p += 2; memcpy(p, kex->x25519_pub, 32); p += 32; @@ -193,6 +193,23 @@ int paze_tls_ext_find_client_key_share(const uint8_t *data, size_t len, return 0; } +/* ============================================================ + * HelloRetryRequest 的 key_share (RFC 8446 §4.2.8): + * body = 仅 selected_group(2 字节),无 share key。 + * ============================================================ */ +size_t paze_tls_ext_write_hrr_key_share(uint8_t *buf, uint16_t group) { + uint8_t *p = buf + 4; + paze_store16_be(p, group); + size_t body = 2; + return put_ext_header(buf, PAZE_TLS_EXT_KEY_SHARE, body) + body; +} + +/* 解析 HRR 的 key_share:body 为 2 字节 group,返回该组(0=失败) */ +uint16_t paze_tls_ext_parse_hrr_key_share(const uint8_t *data, size_t len) { + if (len != 2) return 0; + return paze_load16_be(data); +} + /* ============================================================ * server_name (SNI) * list length(2) || list: name_type(1) || name_length(2) || name diff --git a/src/ssl/tls_handshake12.c b/src/ssl/tls_handshake12.c index a3b54a8..2279745 100644 --- a/src/ssl/tls_handshake12.c +++ b/src/ssl/tls_handshake12.c @@ -398,6 +398,7 @@ static paze_status_t tls12_client_build_cke(paze_tls_t *tls, * ============================================================ */ paze_status_t paze_tls12_client_handshake(paze_tls_t *tls) { paze_status_t st; + tls->proto13 = 0; /* 1.3 回退到 1.2:record 层必须切回 1.2 分支 */ /* ---- 阶段 A:发送 ClientHello / 等待并解析 ServerHello ---- */ if (tls->hs_state == PAZE_HS_INIT) { @@ -691,6 +692,7 @@ paze_status_t paze_tls12_server_handshake(paze_tls_t *tls) { paze_tls_config_t *cfg = tls->cfg_owned; paze_status_t st; uint16_t client_suites[64]; size_t n_suites = 0; + tls->proto13 = 0; /* 1.3 回退到 1.2:record 层必须切回 1.2 分支 */ /* ---- 阶段 A:解析 ClientHello、选择参数 ---- */ if (tls->hs_state == PAZE_HS_INIT) { diff --git a/src/ssl/tls_handshake13.c b/src/ssl/tls_handshake13.c index fbac332..2976803 100644 --- a/src/ssl/tls_handshake13.c +++ b/src/ssl/tls_handshake13.c @@ -24,13 +24,33 @@ uint8_t *paze_tls_hs_put_header(uint8_t *msg, uint8_t type, size_t body_len) { return msg; } -paze_status_t paze_tls_kex_generate(paze_tls_kex_t *kex, int prefer_p256) { +static paze_status_t paze_tls_kex_gen_p256(paze_tls_kex_t *kex); + +paze_status_t paze_tls_kex_generate(paze_tls_kex_t *kex, uint16_t group_hint) { paze_memzero(kex, sizeof(*kex)); + int prefer_p256 = (group_hint == PAZE_TLS_GROUP_SECP256R1); + int only_x = (group_hint == PAZE_TLS_GROUP_X25519); kex->kind = prefer_p256 ? PAZE_KEX_P256 : PAZE_KEX_X25519; + if (only_x) { + /* 仅生成 X25519 密钥对(其余清零) */ + if (paze_random(kex->x25519_priv, 32) != PAZE_OK) return PAZE_ERR_PLATFORM; + if (paze_x25519_public(kex->x25519_pub, kex->x25519_priv) != 0) return PAZE_ERR; + return PAZE_OK; + } + if (prefer_p256) { + /* 仅生成 P-256 密钥对 */ + return paze_tls_kex_gen_p256(kex); + } + + /* 默认:X25519 优先(仅发 X25519 share;P-256 密钥对也生成备用) */ if (paze_random(kex->x25519_priv, 32) != PAZE_OK) return PAZE_ERR_PLATFORM; if (paze_x25519_public(kex->x25519_pub, kex->x25519_priv) != 0) return PAZE_ERR; + return paze_tls_kex_gen_p256(kex); +} +/* 生成 P-256 密钥对(priv 归一化到 [1, n-1],pub = 0x04||X||Y) */ +static paze_status_t paze_tls_kex_gen_p256(paze_tls_kex_t *kex) { if (paze_random(kex->p256_priv, 32) != PAZE_OK) return PAZE_ERR_PLATFORM; paze_bn_t d, n, tmp; paze_p256_load_n(&n); @@ -100,6 +120,17 @@ static paze_status_t recv_hs(paze_tls_t *tls, continue; /* 兼容性 CCS(1.2 互操作):忽略 */ } if (ct != PAZE_TLS_CT_HANDSHAKE) { + if (ct == PAZE_TLS_CT_APPLICATION_DATA && tls->early_read_valid) { + /* 服务端收到的 0-RTT 数据:解密成功(record 层已完成), + * 缓冲起来握手完成后交应用(RFC 8446 §4.2.10)。 */ + if (n <= sizeof(tls->early_buf) - tls->early_len) { + memcpy(tls->early_buf + tls->early_len, buf, n); + tls->early_len += n; + continue; + } + paze_tls_set_err(tls, "early data buffer overflow"); + return PAZE_ERR_OVERFLOW; + } if (!tls->handshake_done) { paze_tls_set_err(tls, "unexpected ct %d during hs", ct); return PAZE_ERR_PROTOCOL; @@ -169,6 +200,13 @@ paze_status_t paze_tls13_build_ch(paze_tls_t *tls, uint8_t *out, size_t *outlen) n = paze_tls_ext_write_alpn(p, cfg->alpn, cfg->alpn_count); p += n; n = paze_tls_ext_write_renegotiation_info(p); p += n; + /* 0-RTT 声明(RFC 8446 §4.2.10):仅当有可用 PSK 且启用 early 时。 + * HRR 后(CH2)不得再携带 early_data。 */ + if (tls->offered_psk && cfg->enable_early && !tls->had_hrr) { + n = paze_tls_ext_write_early_data(p); p += n; + tls->early_offer = 1; + } + /* PSK 会话恢复:psk_key_exchange_modes + pre_shared_key(必须最后)。 * pre_shared_key 分两段构造:binder 数据不参与 binder 的 transcript hash, * 因此先写 identities + binders 总长度,计算 hash,再填 binder。 */ @@ -207,8 +245,28 @@ paze_status_t paze_tls13_build_ch(paze_tls_t *tls, uint8_t *out, size_t *outlen) size_t ch_partial = (size_t)(p - out); /* binder 计算点 */ uint8_t tr_h[64]; - if (tls->psk_hash == 1) paze_sha384(out, ch_partial, tr_h); - else paze_sha256(out, ch_partial, tr_h); + if (tls->had_hrr) { + /* CH2 的 binder(RFC 8446 §4.2.11):Hash(MessageHash(CH1) || HRR || + * CH2-partial),MessageHash 用 PSK 关联 hash 构造;CH1/HRR 需在 + * 客户端收到 HRR 时保存(build_ch 之前 transcript 已重置)。 */ + const paze_hash_alg_t *Hh = (tls->psk_hash == 1) ? paze_hash_sha384() : paze_hash_sha256(); + uint8_t hc[PAZE_HASH_MAX_CTX]; + Hh->init(hc); + uint8_t mh[64]; + if (tls->psk_hash == 1) paze_sha384(tls->ch1, tls->ch1_len, mh); + else paze_sha256(tls->ch1, tls->ch1_len, mh); + uint8_t hdr[4] = { PAZE_TLS_HS_MESSAGE_HASH, 0, + (uint8_t)(Hh->digest_len >> 8), (uint8_t)Hh->digest_len }; + Hh->update(hc, hdr, 4); + Hh->update(hc, mh, Hh->digest_len); + if (tls->hrr_raw) Hh->update(hc, tls->hrr_raw, tls->hrr_raw_len); + Hh->update(hc, out, ch_partial); + Hh->final(hc, tr_h); + } else if (tls->psk_hash == 1) { + paze_sha384(out, ch_partial, tr_h); + } else { + paze_sha256(out, ch_partial, tr_h); + } uint8_t binder[64]; st = paze_tls13_compute_binder(tls, tr_h, hl, binder); if (st != PAZE_OK) return st; @@ -306,10 +364,8 @@ 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) { - fprintf(stderr, "[cv] ECDSA leaf but sig_alg=%04x\n", sig_alg); + if (sig_alg != PAZE_TLS_SIG_ECDSA_SECP256R1_SHA256) 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); } @@ -374,12 +430,21 @@ static int ee_alpn_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { return 0; } +/* For EE early_data presence (server accepted 0-RTT) */ +static int ee_early_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { + int *ok = (int *)x; + if (tp == PAZE_TLS_EXT_EARLY_DATA) *ok = 1; + (void)b; (void)l; + return 0; +} + /* For CH parsing: {v, g, s, sni} */ typedef struct { int v; uint16_t g; int s; char sni[256]; + int early; /* CH 携带 early_data 扩展 */ } ch_ext_ctx_t; static int ch_ext_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { @@ -392,6 +457,8 @@ static int ch_ext_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { s->g = paze_tls_ext_parse_supported_groups(b, l); } else if (tp == PAZE_TLS_EXT_SERVER_NAME) { if (paze_tls_ext_parse_sni(b, l, s->sni)) s->s = 1; + } else if (tp == PAZE_TLS_EXT_EARLY_DATA) { + if (paze_tls_ext_parse_early_data(b, l)) s->early = 1; } return 0; } @@ -453,39 +520,188 @@ static void client_recv_new_session_ticket(paze_tls_t *tls) { tls->psk_len = plen; } +/* 客户端:构造并发送 ClientHello(INIT 阶段)。 + * 包括:生成 kex、PSK 准备(binder_key)、build_ch、保存 ch1、 + * 计入 transcript、发送、0-RTT 早期密钥派生。 + * 由握手入口与 paze_tls_early_write 共用;早数据场景下需在 + * paze_tls_handshake() 之前调用(幂等)。 */ +paze_status_t paze_tls13_client_send_ch(paze_tls_t *tls) { + paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg; + paze_status_t st; + + if (tls->ch1) return PAZE_OK; /* 幂等:已发送过 CH1 */ + st = paze_tls_kex_generate(&tls->kex, cfg->kex_group); + if (st != PAZE_OK) return st; + tls->proto13 = 1; /* 0-RTT 加密在 neg.version 确定前就需要 1.3 分支 */ + paze_tls_tr_init(&tls->transcript, paze_hash_sha256()); + + /* PSK 会话恢复准备:若配置了可用会话,设置 offered_psk 并派生 + * binder_key(供 ClientHello 计算 binder)。binder 的 hash 用 PSK + * 关联 hash,与最终协商的 cipher 无关。 */ + if (cfg->session.valid && cfg->session.psk_len > 0 && cfg->session.ticket_len > 0) { + tls->offered_psk = 1; + tls->psk_hash = cfg->session.hash; + tls->psk_len = cfg->session.psk_len; + memcpy(tls->psk, cfg->session.psk, cfg->session.psk_len); + st = paze_tls13_psk_early_secret(tls, cfg->session.psk, + cfg->session.psk_len, cfg->session.hash); + if (st != PAZE_OK) return st; + } + + uint8_t ch[PAZE_TLS_MAX_RECORD]; size_t ch_len = 0; + st = paze_tls13_build_ch(tls, ch, &ch_len); + if (st != PAZE_OK) return st; + /* 保存 ClientHello 原始消息:供 SHA-384 transcript 重放 / HRR 合成 */ + free(tls->ch1); + tls->ch1 = (uint8_t *)malloc(ch_len); + if (!tls->ch1) return PAZE_ERR_PLATFORM; + memcpy(tls->ch1, ch, ch_len); + tls->ch1_len = ch_len; + paze_tls_tr_update(&tls->transcript, ch, ch_len); + st = paze_tls_record_send_plain(tls, PAZE_TLS_CT_HANDSHAKE, ch, ch_len); + if (st != PAZE_OK) return st; + paze_tls_tr_snapshot(&tls->transcript); + + /* 0-RTT:CH 携带 early_data → 派生 early_write 密钥并切换写密钥。 + * 之后 paze_tls_early_write() 写出的记录都用该密钥加密。 */ + if (tls->early_offer) { + st = paze_tls13_derive_early_key(tls, 1, PAZE_AEAD_AES_128_GCM); + if (st != PAZE_OK) return st; + tls->early_sent = 1; + tls->write_key = &tls->early_write; + tls->enc_write = 1; + } + return PAZE_OK; +} + +/* 客户端:按最终协商 hash 重建 transcript。 + * 覆盖两种情况: + * - 无 HRR:Hash(CH1 || SH) + * - 有 HRR:Hash(MessageHash(CH1) || HRR || CH2 || SH) + * 其中 MessageHash(CH1) = 254 || 3 字节长度 || Hash(CH1)(RFC 8446 §4.4.1)。 */ +static paze_status_t client_rebuild_transcript(paze_tls_t *tls, + const uint8_t *sh_raw, size_t sh_raw_len) { + const paze_hash_alg_t *H = tls->neg.hash; + size_t hl = H->digest_len; + paze_tls_tr_init(&tls->transcript, H); + if (tls->had_hrr) { + if (!tls->ch1 || !tls->hrr_raw || !tls->ch2) + return PAZE_ERR_PROTOCOL; + uint8_t h[64]; + if (hl == 48) paze_sha384(tls->ch1, tls->ch1_len, h); + else paze_sha256(tls->ch1, tls->ch1_len, h); + uint8_t hdr[4] = { PAZE_TLS_HS_MESSAGE_HASH, 0, + (uint8_t)(hl >> 8), (uint8_t)hl }; + paze_tls_tr_update(&tls->transcript, hdr, 4); + paze_tls_tr_update(&tls->transcript, h, hl); + paze_tls_tr_update(&tls->transcript, tls->hrr_raw, tls->hrr_raw_len); + paze_tls_tr_update(&tls->transcript, tls->ch2, tls->ch2_len); + } else { + if (tls->ch1) paze_tls_tr_update(&tls->transcript, tls->ch1, tls->ch1_len); + } + paze_tls_tr_update(&tls->transcript, sh_raw, sh_raw_len); + return PAZE_OK; +} + +/* 客户端:处理 HelloRetryRequest(RFC 8446 §4.1.4)。 + * 解析选定组 → 重建 kex(新密钥对)→ 放弃 0-RTT → 重置 transcript 为 + * MessageHash(CH1)||HRR → 发送 CH2(仅选定组 share)。返回后继续等 ServerHello。 */ +typedef struct { + int v; /* supported_versions 含 1.3 */ + uint16_t g; /* HRR 选定组 */ + int fg; /* 是否含 key_share */ +} hrr_ext_t; + +static int hrr_ext_v_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { + int *v = (int *)x; + if (tp == PAZE_TLS_EXT_SUPPORTED_VERSIONS) { + int pick = 0; + if (paze_tls_ext_parse_supported_versions(b, l, &pick) && pick == PAZE_TLS_VERSION_1_3) + *v = 1; + } + return 0; +} + +static int hrr_ext_ks_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { + hrr_ext_t *h = (hrr_ext_t *)x; + if (tp == PAZE_TLS_EXT_KEY_SHARE) { + uint16_t g = paze_tls_ext_parse_hrr_key_share(b, l); + if (g) { h->g = g; h->fg = 1; } + } + return 0; +} + +static paze_status_t client_handle_hrr(paze_tls_t *tls, + const uint8_t *sh_raw, size_t sh_raw_len, + const uint8_t *body, size_t blen) { + if (blen < 2 + 32 + 1 + 2 + 1) return PAZE_ERR_FORMAT; + const uint8_t *bp = body + 2 + 32; + uint8_t sid_len = *bp++; + if (blen < (size_t)(bp - body) + sid_len + 2 + 1 + 2) return PAZE_ERR_FORMAT; + bp += sid_len; + bp += 2; /* cipher(HRR 含服务端偏好套件,不采用) */ + bp++; /* comp */ + size_t ext_total = paze_load16_be(bp); bp += 2; + if (blen != (size_t)(bp - body) + ext_total) return PAZE_ERR_FORMAT; + + /* 解析 HRR 扩展:supported_versions 必须为 1.3;key_share 取选定组 */ + hrr_ext_t hx = { 0, 0, 0 }; + const uint8_t *hrr_exts = bp - 2; + paze_tls_extensions_foreach(hrr_exts, 2 + ext_total, hrr_ext_v_cb, &hx.v); + paze_tls_extensions_foreach(hrr_exts, 2 + ext_total, hrr_ext_ks_cb, &hx); + if (!hx.v || !hx.fg) { + paze_tls_set_err(tls, "HRR missing required ext"); + return PAZE_ERR_PROTOCOL; + } + if (hx.g != PAZE_TLS_GROUP_X25519 && hx.g != PAZE_TLS_GROUP_SECP256R1) { + paze_tls_set_err(tls, "HRR selected unsupported group %d", hx.g); + return PAZE_ERR_NOTSUP; + } + + tls->had_hrr = 1; + tls->hrr_group = hx.g; + free(tls->hrr_raw); + tls->hrr_raw = (uint8_t *)malloc(sh_raw_len); + if (!tls->hrr_raw) return PAZE_ERR_PLATFORM; + memcpy(tls->hrr_raw, sh_raw, sh_raw_len); + tls->hrr_raw_len = sh_raw_len; + PAZE_LOGD("client: HRR received, selected group %04x", hx.g); + + /* 放弃 0-RTT:不再发送早数据,CH2 不得携带 early_data */ + tls->early_offer = 0; + tls->early_write_valid = 0; + + /* HRR 后必须为新选组生成新的密钥对(RFC 8446 §4.1.4) */ + paze_status_t st = paze_tls_kex_generate(&tls->kex, hx.g); + if (st != PAZE_OK) return st; + + /* transcript: MessageHash(CH1) || HRR(最终重建在收到 SH 后进行) */ + st = paze_tls_tr_reset_to_synthetic(&tls->transcript); + if (st != PAZE_OK) return st; + paze_tls_tr_update(&tls->transcript, tls->hrr_raw, tls->hrr_raw_len); + + /* CH2:仅发送选定组的 key_share(build_ch 依据 had_hrr) */ + uint8_t ch2[PAZE_TLS_MAX_RECORD]; size_t ch2_len = 0; + st = paze_tls13_build_ch(tls, ch2, &ch2_len); + if (st != PAZE_OK) return st; + free(tls->ch2); + tls->ch2 = (uint8_t *)malloc(ch2_len); + if (!tls->ch2) return PAZE_ERR_PLATFORM; + memcpy(tls->ch2, ch2, ch2_len); + tls->ch2_len = ch2_len; + paze_tls_tr_update(&tls->transcript, ch2, ch2_len); + st = paze_tls_record_send_plain(tls, PAZE_TLS_CT_HANDSHAKE, ch2, ch2_len); + if (st != PAZE_OK) return st; + return PAZE_OK; +} + paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg; paze_status_t st; if (tls->hs_state == PAZE_HS_INIT) { - st = paze_tls_kex_generate(&tls->kex, 0); + st = paze_tls13_client_send_ch(tls); if (st != PAZE_OK) return st; - paze_tls_tr_init(&tls->transcript, paze_hash_sha256()); - - /* PSK 会话恢复准备:若配置了可用会话,设置 offered_psk 并派生 - * binder_key(供 ClientHello 计算 binder)。binder 的 hash 用 PSK - * 关联 hash,与最终协商的 cipher 无关。 */ - if (cfg->session.valid && cfg->session.psk_len > 0 && cfg->session.ticket_len > 0) { - tls->offered_psk = 1; - tls->psk_hash = cfg->session.hash; - tls->psk_len = cfg->session.psk_len; - memcpy(tls->psk, cfg->session.psk, cfg->session.psk_len); - st = paze_tls13_psk_early_secret(tls, cfg->session.psk, - cfg->session.psk_len, cfg->session.hash); - if (st != PAZE_OK) return st; - } - - uint8_t ch[PAZE_TLS_MAX_RECORD]; size_t ch_len = 0; - st = paze_tls13_build_ch(tls, ch, &ch_len); - if (st != PAZE_OK) return st; - /* 保存 ClientHello 原始消息:供 SHA-384 transcript 重放 */ - tls->ch1 = (uint8_t *)malloc(ch_len); - if (!tls->ch1) return PAZE_ERR_PLATFORM; - memcpy(tls->ch1, ch, ch_len); - tls->ch1_len = ch_len; - paze_tls_tr_update(&tls->transcript, ch, ch_len); - paze_tls_record_send_plain(tls, PAZE_TLS_CT_HANDSHAKE, ch, ch_len); - paze_tls_tr_snapshot(&tls->transcript); tls->hs_state = PAZE_HS_CL_WAIT_SH; } @@ -499,6 +715,15 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { return PAZE_ERR_PROTOCOL; } if (blen < 2 + 32 + 1 + 2 + 1) return PAZE_ERR_FORMAT; + + /* HelloRetryRequest 识别(RFC 8446 §4.1.3):random == 固定魔数 */ + if (paze_ct_equal(body + 2, paze_tls_hrr_random, 32) == 0) { + if (!sh_raw || sh_raw_len == 0) return PAZE_ERR_PROTOCOL; + st = client_handle_hrr(tls, sh_raw, sh_raw_len, body, blen); + if (st != PAZE_OK) return st; + continue; /* 等第二个 ServerHello */ + } + const uint8_t *bp = body; bp += 2 + 32; /* skip ver + random */ uint8_t sid_len = *bp++; @@ -532,12 +757,11 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { return PAZE_ERR_NOTSUP; } - /* 若协商 hash 为 SHA-384,transcript 需用 SHA-384 从头重算: - * recv_hs 已把 CH+SH 算入 SHA-256 transcript,这里重建为 SHA-384(CH || SH) */ - if (tls->neg.hash == paze_hash_sha384()) { - paze_tls_tr_init(&tls->transcript, paze_hash_sha384()); - if (tls->ch1) paze_tls_tr_update(&tls->transcript, tls->ch1, tls->ch1_len); - if (sh_raw) paze_tls_tr_update(&tls->transcript, sh_raw, sh_raw_len); + /* 按最终协商 hash 重建 transcript(统一处理 SHA-384 与 HRR 情况): + * recv_hs 已把 CH/SH 算入 SHA-256 transcript,这里从原始消息重建。 */ + if (tls->had_hrr || tls->neg.hash == paze_hash_sha384()) { + st = client_rebuild_transcript(tls, sh_raw, sh_raw_len); + if (st != PAZE_OK) return st; } /* Parse SH extensions */ @@ -599,6 +823,12 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { memcpy(tls->neg.alpn, alpn_list[0], an); tls->neg.alpn[an] = 0; } + /* 服务端接受 0-RTT:EE 含 early_data 扩展 */ + if (tls->early_sent) { + int early_ok = 0; + paze_tls_extensions_foreach(body, blen, ee_early_cb, &early_ok); + if (early_ok) tls->early_accepted = 1; + } } } tls->hs_state = PAZE_HS_CL_WAIT_CERT_CR_CV; @@ -694,6 +924,20 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { } } + /* 0-RTT:发送 EndOfEarlyData(RFC 8446 §4.5)。必须用 early 密钥加密, + * 且要计入 transcript(在 server Finished 之后、client Finished 之前)。 + * 此时 app keys 已派生(app keys 的 transcript 不含 EoED)。 */ + if (tls->early_sent && tls->early_write_valid) { + uint8_t eoed[4]; + paze_tls_hs_put_header(eoed, PAZE_TLS_HS_END_OF_EARLY_DATA, 0); + paze_tls_tr_update(&tls->transcript, eoed, 4); + tls->write_key = &tls->early_write; + st = paze_tls_record_send(tls, PAZE_TLS_CT_HANDSHAKE, eoed, 4); + tls->write_key = &tls->handshake_write; + tls->early_write_valid = 0; + if (st != PAZE_OK) return st; + } + uint8_t vd[64]; size_t vd_len = 0; st = build_finished(tls, 0, vd, &vd_len); if (st != PAZE_OK) return st; @@ -792,8 +1036,26 @@ static int server_try_psk(paze_tls_t *tls, const uint8_t *exts, size_t exts_len, if (ch_raw_len < bl) { PAZE_LOGD("psk: ch too short %zu < %zu", ch_raw_len, bl); return 0; } size_t partial_len = ch_raw_len - bl; /* CH 去掉 binder 列表(保留 binders 长度字段) */ uint8_t ph[64]; - if (tls->psk_hash == 1) paze_sha384(ch_raw, partial_len, ph); - else paze_sha256(ch_raw, partial_len, ph); + if (tls->sent_hrr) { + /* CH2 的 binder 覆盖 MessageHash(CH1) || HRR || CH2-partial: + * 客户端在收到 HRR 后按 PSK 关联 hash 重算(RFC 8446 §4.2.11) */ + uint8_t hc[PAZE_HASH_MAX_CTX]; + H->init(hc); + uint8_t mh[64]; + if (tls->psk_hash == 1) paze_sha384(tls->ch1, tls->ch1_len, mh); + else paze_sha256(tls->ch1, tls->ch1_len, mh); + uint8_t hdr[4] = { PAZE_TLS_HS_MESSAGE_HASH, 0, + (uint8_t)(H->digest_len >> 8), (uint8_t)H->digest_len }; + H->update(hc, hdr, 4); + H->update(hc, mh, H->digest_len); + if (tls->hrr_raw) H->update(hc, tls->hrr_raw, tls->hrr_raw_len); + H->update(hc, ch_raw, partial_len); + H->final(hc, ph); + } else if (tls->psk_hash == 1) { + paze_sha384(ch_raw, partial_len, ph); + } else { + paze_sha256(ch_raw, partial_len, ph); + } if (paze_tls13_psk_early_secret(tls, tls->psk, tls->psk_len, tls->psk_hash) != PAZE_OK) { PAZE_LOGD("psk: early secret failed"); @@ -878,12 +1140,13 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { paze_tls_set_err(tls, "expected ClientHello, got %d", type); return PAZE_ERR_PROTOCOL; } - /* 保存 ClientHello 原始消息(含 4 字节头):供协商 hash 重放 */ + /* 保存 ClientHello 原始消息(含 4 字节头):供协商 hash 重放 / HRR 合成 */ if (!tls->ch1 && ch_raw && ch_raw_len > 0) { tls->ch1 = (uint8_t *)malloc(ch_raw_len); if (!tls->ch1) return PAZE_ERR_PLATFORM; memcpy(tls->ch1, ch_raw, ch_raw_len); tls->ch1_len = ch_raw_len; + paze_tls_tr_snapshot(&tls->transcript); /* 供 tr_reset_to_synthetic */ } if (blen < 2 + 32 + 1) return PAZE_ERR_FORMAT; const uint8_t *bp = body; @@ -904,6 +1167,7 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { if (!chosen_cipher) return PAZE_ERR_NOTSUP; /* 无 1.3 套件 → 回退 1.2 */ tls->neg.cipher = chosen_cipher; tls->neg.version = PAZE_TLS_VERSION_1_3; /* 服务端协商为 1.3 */ + tls->proto13 = 1; paze_tls_cipher_params(chosen_cipher, &tls->neg.aead, &tls->neg.hash, &tls->neg.key_len, &tls->neg.hash_len); /* 用协商 hash 重建 transcript;必须重放 ClientHello(recv_hs 已把它 @@ -930,7 +1194,7 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { * 成功则 psk_mode=1,ServerHello 回复 selected_identity, * 握手跳过 Certificate/CertificateVerify。 */ tls->psk_mode = 0; - if (server_try_psk(tls, exts, 2 + ext_total, tls->ch1, tls->ch1_len)) + if (server_try_psk(tls, exts, 2 + ext_total, ch_raw, ch_raw_len)) tls->psk_mode = 1; char sni_buf[PAZE_TLS_SNI_MAX + 1]; @@ -942,26 +1206,127 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { sni_buf[sn] = 0; } - /* Lookup key_share */ - uint16_t select_group = sx.g; + /* CH2(HRR 后)不得再携带 early_data(RFC 8446 §4.1.4) */ + if (tls->sent_hrr && sx.early) { + paze_tls_set_err(tls, "CH2 must not contain early_data"); + return PAZE_ERR_PROTOCOL; + } + if (tls->sent_hrr && !tls->had_hrr) { + /* 记录 CH2 原始消息(重建 transcript 用) */ + free(tls->ch2); + tls->ch2 = (uint8_t *)malloc(ch_raw_len); + if (!tls->ch2) return PAZE_ERR_PLATFORM; + memcpy(tls->ch2, ch_raw, ch_raw_len); + tls->ch2_len = ch_raw_len; + } + + /* Lookup key_share:HRR 后必须提供所选组的 share */ + uint16_t select_group = tls->sent_hrr ? tls->hrr_group : sx.g; const uint8_t *ks_pub = NULL; size_t ks_publen = 0; int have_share = 0; ks_lookup_ctx_t ksl = { select_group, &ks_pub, &ks_publen, &have_share }; paze_tls_extensions_foreach(exts, 2 + ext_total, ks_lookup_cb, &ksl); uint8_t shared[64]; size_t shared_len = 0; + + /* 0-RTT 密钥派生:CH 带 early_data 且 PSK 被接受 → 派生 early_read + * (AEAD = 客户端 CH 第一个套件的 AEAD,RFC 8446 §4.2.10)。 + * 即使随后发 HRR 也派生:客户端在收到 HRR 前可能已发出 0-RTT 数据, + * 服务端须能解密并丢弃,且需解密 CH2 之后的 EoED。 */ + if (sx.early && tls->psk_mode && !tls->had_hrr) { + tls->early_offer = 1; + paze_tls_aead_kind_t ea = PAZE_AEAD_NONE; + const paze_hash_alg_t *eh = NULL; + size_t ekl = 0, ehl = 0; + paze_tls_cipher_params(paze_load16_be(body + 2 + 32 + 1 + sid_len + 2), + &ea, &eh, &ekl, &ehl); + if (ea != PAZE_AEAD_NONE) { + st = paze_tls13_derive_early_key(tls, 0, ea); + if (st != PAZE_OK) return st; + } + } + if (have_share) { - st = paze_tls_kex_generate(&tls->kex, (select_group == PAZE_TLS_GROUP_SECP256R1)); + st = paze_tls_kex_generate(&tls->kex, select_group); if (st != PAZE_OK) return st; st = paze_tls_kex_ecdh(&tls->kex, select_group, ks_pub, ks_publen, shared, &shared_len); if (st != PAZE_OK) return st; + } else if (!tls->psk_mode && !tls->sent_hrr) { + /* 客户端未提供本服务端偏好组的 key_share → 发 HelloRetryRequest, + * 要求用 selected_group 重发 ClientHello(RFC 8446 §4.1.3/§4.1.4)。 */ + uint8_t hrr[PAZE_TLS_MAX_RECORD]; + uint8_t *hp = hrr + 4; + paze_store16_be(hp, PAZE_TLS_LEGACY_VERSION); hp += 2; + memcpy(hp, paze_tls_hrr_random, 32); hp += 32; + const uint8_t *chp = body + 2 + 32; + uint8_t ch_sid = *chp++; + *hp++ = ch_sid; + memcpy(hp, chp, ch_sid); hp += ch_sid; + paze_store16_be(hp, chosen_cipher); hp += 2; + *hp++ = 0x00; + uint8_t *hext = hp; hp += 2; + size_t hn; + hn = paze_tls_ext_write_supported_versions(hp, 0, 1, 0); hp += hn; + hn = paze_tls_ext_write_hrr_key_share(hp, select_group); hp += hn; + paze_store16_be(hext, (uint16_t)(hp - hext - 2)); + size_t hrr_body = (size_t)(hp - (hrr + 4)); + paze_tls_hs_put_header(hrr, PAZE_TLS_HS_SERVER_HELLO, hrr_body); + size_t hrr_total = 4 + hrr_body; + + /* transcript 转为合成 MessageHash(CH1)||HRR(后续按最终 hash 重建) */ + paze_tls_tr_reset_to_synthetic(&tls->transcript); + paze_tls_tr_update(&tls->transcript, hrr, hrr_total); + st = paze_tls_record_send_plain(tls, PAZE_TLS_CT_HANDSHAKE, hrr, hrr_total); + if (st != PAZE_OK) return st; + /* 保存 HRR 原始消息(CH2 处理后重建 transcript 用) */ + free(tls->hrr_raw); + tls->hrr_raw = (uint8_t *)malloc(hrr_total); + if (!tls->hrr_raw) return PAZE_ERR_PLATFORM; + memcpy(tls->hrr_raw, hrr, hrr_total); + tls->hrr_raw_len = hrr_total; + tls->sent_hrr = 1; + tls->hrr_group = select_group; + /* 等待 CH2 期间可能收到用 early key 加密的 0-RTT 数据:安装 + * early_read 以便解密丢弃(record_read 按外层类型区分明文/密文) */ + if (tls->early_read_valid) + tls->read_key = &tls->early_read; + PAZE_LOGD("server: HelloRetryRequest sent, group %04x", select_group); + continue; /* 等 CH2 */ } else if (!tls->psk_mode) { - /* 无 key_share:仅当纯 PSK 恢复(psk_ke)才允许 */ - paze_tls_set_err(tls, "CH missing key share for group %d", select_group); + /* HRR 后仍未提供 key_share → 非法 */ + paze_tls_set_err(tls, "CH2 missing key share for group %d", select_group); return PAZE_ERR_PROTOCOL; } /* psk_ke:shared_len 保持 0,handshake_secret IKM 用 0 */ + /* HRR 后:用最终协商 hash 重建 transcript = + * MessageHash(CH1) || HRR || CH2 (RFC 8446 §4.4.1) */ + if (tls->sent_hrr) { + size_t hl = tls->neg.hash->digest_len; + paze_tls_tr_init(&tls->transcript, tls->neg.hash); + uint8_t h[64]; + if (hl == 48) paze_sha384(tls->ch1, tls->ch1_len, h); + else paze_sha256(tls->ch1, tls->ch1_len, h); + uint8_t hdr[4] = { PAZE_TLS_HS_MESSAGE_HASH, 0, + (uint8_t)(hl >> 8), (uint8_t)hl }; + paze_tls_tr_update(&tls->transcript, hdr, 4); + paze_tls_tr_update(&tls->transcript, h, hl); + if (tls->hrr_raw) { + paze_tls_tr_update(&tls->transcript, tls->hrr_raw, tls->hrr_raw_len); + } else { + paze_tls_set_err(tls, "internal: hrr_raw missing"); + return PAZE_ERR_PROTOCOL; + } + paze_tls_tr_update(&tls->transcript, tls->ch2, tls->ch2_len); + tls->had_hrr = 1; + } + + /* HRR 后不得再接受 0-RTT(RFC 8446 §4.1.4); + * 策略上允许且未发生 HRR 时,EE 回 early_data 扩展 */ + if (tls->early_offer && tls->early_read_valid && + !tls->had_hrr && cfg->enable_early) + tls->early_accept = 1; + /* Build ServerHello */ uint8_t sh[PAZE_TLS_MAX_RECORD]; uint8_t *sp = sh; @@ -1003,6 +1368,11 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { paze_memzero(shared, shared_len); if (st != PAZE_OK) return st; + /* 0-RTT 已接受:在收到 EoED 前用 early_read 解密早数据(未发生 HRR 时) */ + if (tls->early_read_valid && !tls->had_hrr) + tls->read_key = &tls->early_read; + + tls->sent_hrr = 0; tls->hs_state = PAZE_HS_SV_SEND_EE_CERT_CV_FIN; } @@ -1015,6 +1385,10 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { if (cfg->alpn_count > 0) { en = paze_tls_ext_write_alpn(ep, cfg->alpn, 1); ep += en; } + if (tls->early_accept) { + /* 接受 0-RTT:EE 回 early_data 扩展(RFC 8446 §4.2.10) */ + en = paze_tls_ext_write_early_data(ep); ep += en; + } size_t extl = (size_t)(ep - (ext_total_ptr + 2)); paze_store16_be(ext_total_ptr, (uint16_t)extl); size_t body_len = (size_t)(ep - (ee + 4)); @@ -1079,6 +1453,12 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { st = send_hs(tls, PAZE_TLS_HS_FINISHED, vd, vd_len); if (st != PAZE_OK) return st; + /* 派生 app keys:transcript = CH...server Finished(不含 EoED/client Finished)。 + * 必须在收到客户端 EoED/Finished 之前派生,否则 transcript 会混入 + * EoED 导致 app traffic secrets 与客户端不一致(RFC 8446 §7.1)。 */ + st = paze_tls13_derive_app_keys(tls); + if (st != PAZE_OK) return st; + tls->hs_state = PAZE_HS_SV_WAIT_FINISHED; } @@ -1089,7 +1469,15 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { if (st != PAZE_OK) return st; if (type == PAZE_TLS_HS_CERTIFICATE) continue; if (type == PAZE_TLS_HS_CERTIFICATE_VERIFY) continue; - if (type == PAZE_TLS_HS_END_OF_EARLY_DATA) continue; + if (type == PAZE_TLS_HS_END_OF_EARLY_DATA) { + /* 客户端早数据结束:切换到 handshake 读密钥(RFC 8446 §4.5)。 + * EoED 已由 recv_hs 计入 transcript(client Finished 验证需要)。 */ + if (tls->early_read_valid) { + tls->read_key = &tls->handshake_read; + tls->early_read_valid = 0; + } + continue; + } if (type != PAZE_TLS_HS_FINISHED) { paze_tls_set_err(tls, "expected client Finished, got %d", type); return PAZE_ERR_PROTOCOL; @@ -1097,10 +1485,8 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { st = verify_finished(tls, 0, body, blen); if (st != PAZE_OK) { paze_tls_set_err(tls, "client Finished verify failed"); return PAZE_ERR_VERIFY; } - /* 先派生 app keys:其 transcript 为 CH...server Finished(不含 client Finished)。 - * 再把 client Finished 计入 transcript,供 resumption_master_secret 使用。 */ - st = paze_tls13_derive_app_keys(tls); - if (st != PAZE_OK) return st; + /* 把 client Finished 计入 transcript,供 resumption_master_secret 使用。 + * (app keys 已在发完 server Finished 后派生,不受影响。) */ if (raw && raw_len) paze_tls_tr_update(&tls->transcript, raw, raw_len); if (paze_tls13_derive_resumption_master(tls) != PAZE_OK) { paze_tls_set_err(tls, "derive resumption master failed"); diff --git a/src/ssl/tls_internal.h b/src/ssl/tls_internal.h index 60ca599..84aabb0 100644 --- a/src/ssl/tls_internal.h +++ b/src/ssl/tls_internal.h @@ -43,6 +43,12 @@ #define PAZE_TLS_MAX_ALPN 8 #define PAZE_TLS_SNI_MAX 255 +/* HelloRetryRequest 魔数 random (RFC 8446 §4.1.3) */ +#define PAZE_TLS_HRR_RANDOM_BYTES \ + 0xCF,0x21,0xAD,0x74,0xE5,0x9A,0x61,0x11,0xBE,0x1D,0x8C,0x02,0x1E,0x65,0xB8,0x91, \ + 0xC2,0xA2,0x11,0x16,0x7A,0xBB,0x8C,0x5E,0x07,0x9E,0x09,0xE2,0xC8,0xA8,0x33,0x9C +static const uint8_t paze_tls_hrr_random[32] = { PAZE_TLS_HRR_RANDOM_BYTES }; + /* AEAD 种类 */ typedef enum { PAZE_AEAD_NONE = 0, @@ -132,6 +138,9 @@ struct paze_tls_config { /* 0-RTT: 客户端启用(服务端默认接受,1=接受) */ int enable_early; + /* 客户端 key_share 限制:0=默认(X25519 优先); + * PAZE_TLS_GROUP_X25519 / PAZE_TLS_GROUP_SECP256R1 = 仅发该组 share(用于测试/限组) */ + uint16_t kex_group; }; /* ============================================================ @@ -214,6 +223,7 @@ struct paze_tls { uint8_t client_finished_key[64]; uint8_t server_finished_key[64]; uint8_t binder_key[64]; + uint8_t binder_finished_key[64]; /* binder 用 finished_key(RFC 8446 §4.2.11.2) */ uint8_t resumption_master_secret[64]; int resumption_master_valid; @@ -260,6 +270,7 @@ struct paze_tls { int early_sent; /* 客户端已发送 0-RTT 数据 */ int early_accepted; /* 服务端 EE 含 early_data(客户端视角) */ int early_accept; /* 服务端接受 0-RTT(服务端视角) */ + int proto13; /* 已进入 1.3 握手(record 层分支用,neg.version 未定前) */ paze_tls_traffic_key_t early_read; paze_tls_traffic_key_t early_write; int early_read_valid; @@ -354,8 +365,10 @@ paze_status_t paze_tls13_derive_app_keys(paze_tls_t *tls); /* 0-RTT early traffic key: * client: 由 early_secret + hash(CH) 派生 "c e traffic" → early_write * server: 同上 → early_read - * is_client 决定写入哪个方向;aead 由调用方从协商/首选套件决定。 */ -paze_status_t paze_tls13_derive_early_key(paze_tls_t *tls, int is_client); + * is_client 决定写入哪个方向;aead 为 0-RTT 使用的 AEAD(客户端 = CH 首选套件的 + * AEAD;服务端 = 客户端 CH 第一个套件的 AEAD,见 RFC 8446 §4.2.10)。 */ +paze_status_t paze_tls13_derive_early_key(paze_tls_t *tls, int is_client, + paze_tls_aead_kind_t aead); /* 由 traffic secret 派生 traffic key 对象 */ paze_status_t paze_tls13_make_traffic_key(paze_tls_traffic_key_t *tk, const paze_hash_alg_t *alg, @@ -406,6 +419,9 @@ int paze_tls_ext_parse_early_data(const uint8_t *data, size_t len); /* 服务端:构造 key_share 扩展(单 group) */ size_t paze_tls_ext_write_key_share_server(uint8_t *buf, uint16_t group, const uint8_t *pub, size_t publen); +/* HelloRetryRequest 的 key_share:仅 selected_group(2 字节,无 share key) */ +size_t paze_tls_ext_write_hrr_key_share(uint8_t *buf, uint16_t group); +uint16_t paze_tls_ext_parse_hrr_key_share(const uint8_t *data, size_t len); /* psk_key_exchange_modes(客户端): psk_ke + psk_dhe_ke */ size_t paze_tls_ext_write_psk_key_exchange_modes(uint8_t *buf); /* pre_shared_key(客户端): identities(1) + binder(1)。返回写入字节数。 */ @@ -448,7 +464,9 @@ int paze_tls_extensions_foreach(const uint8_t *exts, size_t exts_len, uint8_t *paze_tls_hs_put_header(uint8_t *msg, uint8_t type, size_t body_len); /* kex 生成 + ECDHE 共享计算 */ -paze_status_t paze_tls_kex_generate(paze_tls_kex_t *kex, int prefer_p256); +/* group_hint: 0=默认(双组,X25519 优先); + * PAZE_TLS_GROUP_X25519 / PAZE_TLS_GROUP_SECP256R1 = 仅生成该组密钥对 */ +paze_status_t paze_tls_kex_generate(paze_tls_kex_t *kex, uint16_t group_hint); paze_status_t paze_tls_kex_ecdh(paze_tls_kex_t *mine, uint16_t server_group, const uint8_t *peer_pub, size_t peer_pub_len, uint8_t *shared_out, size_t *shared_len); @@ -456,6 +474,9 @@ paze_status_t paze_tls_kex_ecdh(paze_tls_kex_t *mine, uint16_t server_group, /* 1.3 握手 (由 tls_client/tls_server 调用) */ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls); paze_status_t paze_tls13_server_handshake(paze_tls_t *tls); +/* 客户端:构造并发送 ClientHello(含 PSK binder 与 0-RTT 密钥派生)。 + * 由握手入口与 paze_tls_early_write 共用,可重复安全调用(幂等)。 */ +paze_status_t paze_tls13_client_send_ch(paze_tls_t *tls); /* 构造兼容 CH(TLS 1.3 + 1.2 套件/扩展)。TLS 1.2 独立模式复用。 */ paze_status_t paze_tls13_build_ch(paze_tls_t *tls, uint8_t *out, size_t *outlen); /* TLS 1.2 握手 */ diff --git a/src/ssl/tls_keys.c b/src/ssl/tls_keys.c index 9d0959f..3de1f7e 100644 --- a/src/ssl/tls_keys.c +++ b/src/ssl/tls_keys.c @@ -22,6 +22,7 @@ * TLS 1.2 PRF (RFC 5246 §5): P_hash(secret, seed) 分块 HMAC,拼接至 outlen。 */ #include "tls_internal.h" +#include /* ---- cipher suite -> 参数 ---- */ void paze_tls_cipher_params(uint16_t cipher, @@ -240,7 +241,14 @@ paze_status_t paze_tls13_psk_early_secret(paze_tls_t *tls, size_t hl = H->digest_len; uint8_t zero[64]; memset(zero, 0, sizeof(zero)); paze_hkdf_extract(H, zero, hl, psk, psk_len, tls->early_secret); - return paze_tls_derive_secret(H, tls->early_secret, hl, "res binder", NULL, tls->binder_key); + paze_status_t st = paze_tls_derive_secret(H, tls->early_secret, hl, "res binder", NULL, tls->binder_key); + /* RFC 8446 §4.2.11.2: binder 与 Finished 同构,BaseKey 为 binder_key: + * finished_key = HKDF-Expand-Label(binder_key, "finished", "", Hash.length) + * (context 为空字符串,与 Finished 的 finished_key 一致,而非 Hash(""))。 */ + if (st == PAZE_OK) + st = paze_tls_hkdf_expand_label(H, tls->binder_key, hl, "finished", + NULL, 0, tls->binder_finished_key, hl); + return st; } /* 0-RTT early traffic key (RFC 8446 §7.1): @@ -248,8 +256,9 @@ paze_status_t paze_tls13_psk_early_secret(paze_tls_t *tls, * → client 侧为 early_write;server 侧为 early_read。 * hash 用 PSK 关联 hash;transcript 取"最终 ClientHello"的哈希(直接用原始字节算, * 因为 early_secret 的 hash 可能不同于 transcript 对象的协商 hash)。 - * aead 由调用方经 tls->neg.aead(已协商)或客户端首选套件决定。 */ -paze_status_t paze_tls13_derive_early_key(paze_tls_t *tls, int is_client) { + * aead 由调用方指定(RFC 8446 §4.2.10:0-RTT 用客户端 CH 第一个套件的 AEAD)。 */ +paze_status_t paze_tls13_derive_early_key(paze_tls_t *tls, int is_client, + paze_tls_aead_kind_t aead) { const paze_hash_alg_t *H = psk_hash_of(tls->psk_hash); size_t hl = H->digest_len; const uint8_t *ch = tls->ch1 ? tls->ch1 : NULL; @@ -263,13 +272,11 @@ paze_status_t paze_tls13_derive_early_key(paze_tls_t *tls, int is_client) { if (H->digest_len == 48) paze_sha384(ch, chl, th); else paze_sha256(ch, chl, th); - uint8_t c_e[64]; + uint8_t c_e[64]; paze_status_t st = paze_tls_hkdf_expand_label(H, tls->early_secret, hl, "c e traffic", th, hl, c_e, hl); if (st != PAZE_OK) return st; - /* 客户端在收到 ServerHello 前派生:aead 用 CH 首选套件(1301 → AES-128-GCM) */ - paze_tls_aead_kind_t aead = tls->neg.aead; - if (is_client && aead == PAZE_AEAD_NONE) aead = PAZE_AEAD_AES_128_GCM; + if (aead == PAZE_AEAD_NONE) aead = PAZE_AEAD_AES_128_GCM; st = paze_tls13_make_traffic_key(is_client ? &tls->early_write : &tls->early_read, H, c_e, hl, aead); paze_memzero(c_e, hl); @@ -280,13 +287,14 @@ paze_status_t paze_tls13_derive_early_key(paze_tls_t *tls, int is_client) { return st; } -/* binder = HMAC(binder_key, transcript_hash); 输出长度 = H->digest_len */ +/* binder = HMAC(finished_key, transcript_hash) (RFC 8446 §4.2.11.2); + * 输出长度 = H->digest_len */ paze_status_t paze_tls13_compute_binder(paze_tls_t *tls, const uint8_t *tr_hash, size_t tr_hash_len, uint8_t *binder_out) { const paze_hash_alg_t *H = psk_hash_of(tls->psk_hash); - return paze_hmac(H, tls->binder_key, H->digest_len, tr_hash, tr_hash_len, - binder_out); + return paze_hmac(H, tls->binder_finished_key, H->digest_len, + tr_hash, tr_hash_len, binder_out); } /* resumption_master_secret = Derive-Secret(master_secret, "res master", transcript) diff --git a/src/ssl/tls_record.c b/src/ssl/tls_record.c index 70a8055..dcf82a1 100644 --- a/src/ssl/tls_record.c +++ b/src/ssl/tls_record.c @@ -23,6 +23,7 @@ * 缓冲: in_buf 维护已读未解析字节流;每次 record_read 消费一条完整 record。 */ #include "tls_internal.h" +#include /* 1.3 nonce: iv XOR (0^4 || seq_be8) */ static void nonce13(uint8_t nonce[12], const uint8_t iv[12], uint64_t seq) { @@ -86,8 +87,13 @@ paze_status_t paze_tls_record_read(paze_tls_t *tls, return PAZE_OK; } - /* 明文 record(握手初期 / Alert) */ - if (!tls->enc_read || !tls->read_key || tls->neg.version == 0) { + /* 明文 record(握手初期 / Alert)。1.3 下加密 record 的外层类型一律 + * APPLICATION_DATA,故:读密钥已安装但 enc_read=0 时(如服务端发 HRR 后 + * 等待 CH2,期间可能收到用 early key 加密的 0-RTT 数据),只有 + * APPLICATION_DATA 走解密,CH2/Alert 仍按明文处理。 */ + if (!tls->read_key || tls->neg.version == 0 || + (tls->neg.version == PAZE_TLS_VERSION_1_3 && !tls->enc_read && + rtype != PAZE_TLS_CT_APPLICATION_DATA)) { *content_type = rtype; *out = body; *outlen = rlen; @@ -184,7 +190,9 @@ paze_status_t paze_tls_record_send_plain(paze_tls_t *tls, uint8_t content_type, paze_status_t paze_tls_record_send(paze_tls_t *tls, uint8_t content_type, const uint8_t *frag, size_t len) { - if (!tls->enc_write || !tls->write_key || tls->neg.version == 0) + /* neg.version 不能作判据:0-RTT 时客户端在收到 ServerHello 前就用 + * early key 加密发送(neg.version 仍为 0),此时必须走 1.3 加密。 */ + if (!tls->enc_write || !tls->write_key) return paze_tls_record_send_plain(tls, content_type, frag, len); if (len > PAZE_TLS_MAX_RECORD) return PAZE_ERR_OVERFLOW; @@ -192,7 +200,7 @@ paze_status_t paze_tls_record_send(paze_tls_t *tls, uint8_t content_type, uint8_t *o = tls->out_buf; size_t off = 0; - if (tls->neg.version == PAZE_TLS_VERSION_1_3) { + if (tls->proto13) { /* plaintext = frag || content_type */ uint8_t pt[PAZE_TLS_MAX_RECORD + 1]; memcpy(pt, frag, len);