实现 TLS 1.3 0-RTT 数据路径与 HRR(HelloRetryRequest): 客户端 early_data 扩展+握手前早数据+EndOfEarlyData, 服务端 early_read 解密接受; HRR 发送(魔数 random)/合成 MessageHash transcript/CH2 单组响应/HRR 后放弃 0-RTT; 新增公共 API(enable_early_data/early_write/early_data_accepted/early_data_len/hrr_occurred)与 pazessl -early/-key_share 选项; 端到端验证通过(early accepted + hrr=YES); 清理调试残留; STATUS/AGENT 更新
这个提交包含在:
@@ -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 等外部密码学或协议库。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、总体架构
|
|
||||||
|
|
||||||
```
|
```
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
include/paze/ 公共头文件(tls.h 对外 API)
|
||||||
│ apps/ CLI 层 │
|
src/crypto/ 密码学原语:哈希/AES/ChaCha20/Poly1305/AEAD/X25519/Ed25519/bignum/RSA/P-256/KDF/ASN.1/X.509
|
||||||
│ pazessl/ SSH/ sshd/ │
|
src/ssl/ TLS 1.2+1.3:tls_record(记录层) tls_keys(key schedule) tls_extensions(扩展)
|
||||||
│ (s_client/s_server s_time verify x509) (ssh client) (sshd) │
|
tls_handshake13/12(状态机) tls_transcript tls_conn(API/生命周期) tls_client/server(入口)
|
||||||
├──────────┬──────────────────────────────────────────────────────┤
|
src/ssh/ SSH-2:transport/kex/auth/channel/client/server/sftp
|
||||||
│ src/ssl │ src/ssh │
|
src/util/ 内存/字节序/错误/日志
|
||||||
│ 100k预算 │ 30k预算 │
|
apps/ 命令行:pazessl / pssh(分发) / 独立命令(pscp/pkeygen/psshd/psftp…)
|
||||||
│ │ │
|
build/ 中间产物(编译)/测试脚本/互操作脚本
|
||||||
│ tls_bio │ ssh_packet (binary packet 二进制包层) │
|
bin/ 构建输出(exe)
|
||||||
│ tls_record │ ssh_transport (transport layer 传输层) │
|
STATUS.md 完成度总表 AGENT.md 本文件
|
||||||
│ 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 │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## 硬性规则
|
||||||
|
|
||||||
## 二、行数预算(严格上限)
|
1. **目标平台 Los4**:不实现 Windows/Linux/macOS 编译目标(当前平台仅作构建宿主)。
|
||||||
|
2. **零第三方依赖**:仅 ISO C17 + libc;不用 OpenSSL/mbedTLS/GMP;熵来自系统调用。
|
||||||
| 层级 / 模块 | 预算 | 已完成(2026-08-12) | 状态 |
|
3. **结构分层**:`crypto 原语 → ssl/ssh 协议 → apps 命令`,协议层不直接依赖应用层。
|
||||||
|---|---|---|---|
|
4. **常量时间**:密码学核心路径禁止密钥相关分支(用 `paze_ct_equal` 等)。
|
||||||
| **crypto + util + include** | **≤ 10,000** | **6,510** | ✅ 完成(含 fe25519.c) |
|
5. **编码风格**:纯 C、紧凑直接、注释用中文、不做多余抽象层、单文件状态机。
|
||||||
| **src/ssl (PazeSSL/TLS)** | **≤ 60,000** | **2,328** | ✅ TLS 1.3 完成 (extensions/handshake13/client/server/conn) |
|
6. **构建**:`.\build.ps1`(gcc,C17),产物进 `bin/`,中间件留 `build/`。
|
||||||
| **src/ssh (PazeSSH/SSH2)** | **≤ 25,000** | **3,779** | ✅ SSH2 全部模块完成 (packet/transport/kex/keys/auth/channel/client/server/scp/agent) |
|
7. **测试**:`build/run_tls_test.ps1` 自测、`build/run_openssl_interop.ps1` 互操作;改动后必须跑通自测。
|
||||||
| **apps/** | ≤ 3,000 | 1,839 | ✅ pssh 单可执行(ssh/keygen/keyscan/copy-id/agent/add/sshd/scp) + pazessl |
|
8. **Git**:中文提交信息;不主动推送,用户要求才推(parlz=gitea、origin=GitHub);无 push 权限/网络问题时如实报告。
|
||||||
| **tests/** | ≤ 2,000 | 1,688 | ✅ paze_kat 统一测试 + 11 个独立 KAT 可执行程序 |
|
9. **文档**:改功能同步更新 STATUS.md;不建多余 .md。
|
||||||
| **合计** | **≤ 100,000** | **16,144** | **83,856 剩余** |
|
|
||||||
|
## 待办(按优先级)
|
||||||
> **关键红线**:
|
|
||||||
> - SSL 部分(src/ssl + include/paze/tls.h)绝对不可超过 **60,000 行**。
|
### 1. 0-RTT / HRR — TLS 1.3 数据路径(✅ 已完成 2026-08-13)
|
||||||
> - SSH 部分(src/ssh)绝对不可超过 **25,000 行**。
|
- 服务端:CH 无 key_share → 发 HRR(random=魔数 `CF21AD74...F84A4F`)→ 等 CH2;`sent_hrr` 标记
|
||||||
> - 任一模块若超限,必须精简:移除冷门特性、压缩 verbose 日志、合并小函数。
|
- 客户端:识别 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 通过
|
||||||
|
|
||||||
## 三、TLS 模块设计 (PazeSSL)
|
### 2. TLS secp384r1
|
||||||
|
实现 P-384 点运算(bignum 已有),加曲线 + 套件,中等偏繁。
|
||||||
### 3.1 公共 API (`include/paze/tls.h`, 对外不透明)
|
|
||||||
```c
|
### 3. SSH ProxyJump
|
||||||
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 <winsock2.h>
|
|
||||||
typedef SOCKET sock_t;
|
|
||||||
#define INVALID INVALID_SOCKET
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
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 <host:port> [-CAfile roots.pem] [-servername SNI] [-alpn h2,http1.1]
|
|
||||||
pazessl s_server -accept <port> -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 <host:port> [-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 加密段)
|
|
||||||
|
|||||||
@@ -30,7 +30,8 @@
|
|||||||
- ✅ 证书链与信任锚加载、RSA/ECDSA 私钥 PEM 解析、**X.509 ECDSA 证书(P-256)解析与验签**(`pazessl verify -CAfile ec_cert.pem ec_cert.pem` 通过)
|
- ✅ 证书链与信任锚加载、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 ECDSA CertificateVerify(RFC 8446 §4.2.3 DER 签名)与 OpenSSL 双向互操作
|
||||||
- ✅ TLS 1.3 SHA-384 密码套件(TLS_AES_256_GCM_SHA384)服务端/客户端握手(含 PSK 恢复的 resumption_master_secret)
|
- ✅ 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(✅ 完成)
|
## PazeSSH — SSH-2(✅ 完成)
|
||||||
|
|
||||||
@@ -69,4 +70,4 @@
|
|||||||
|
|
||||||
## 遗留项(已知)
|
## 遗留项(已知)
|
||||||
|
|
||||||
- 0-RTT 数据路径与 HRR(HelloRetryRequest)未实现
|
- 无(TLS 1.2 / 1.3 与 SSH-2 全部既定功能完成)
|
||||||
|
|||||||
+40
-9
@@ -9,6 +9,7 @@
|
|||||||
* x509 - 打印证书
|
* x509 - 打印证书
|
||||||
*/
|
*/
|
||||||
#include "paze/tls.h"
|
#include "paze/tls.h"
|
||||||
|
#include "paze/log.h"
|
||||||
#include "paze/encoding.h"
|
#include "paze/encoding.h"
|
||||||
#include "paze/rsa.h"
|
#include "paze/rsa.h"
|
||||||
#include "paze/x509.h"
|
#include "paze/x509.h"
|
||||||
@@ -70,8 +71,9 @@ static int usage(void) {
|
|||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"PazeSSL v0.1 (libc-only TLS 1.3 + 1.2)\n"
|
"PazeSSL v0.1 (libc-only TLS 1.3 + 1.2)\n"
|
||||||
"Usage:\n"
|
"Usage:\n"
|
||||||
" pazessl s_client -connect host:port [-CAfile roots.pem] [-servername SNI] [-tls1_2]\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"
|
" [-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 verify -CAfile roots.pem cert.pem\n"
|
||||||
" pazessl genrsa -out key.pem [2048]\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"
|
" 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 *sni = NULL;
|
||||||
const char *sess_in = NULL, *sess_out = NULL;
|
const char *sess_in = NULL, *sess_out = NULL;
|
||||||
const char *msg = 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++) {
|
for (int i = 2; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "-connect") == 0 && i + 1 < argc) {
|
if (strcmp(argv[i], "-connect") == 0 && i + 1 < argc) {
|
||||||
char buf[256]; snprintf(buf, sizeof(buf), "%s", argv[++i]);
|
char buf[256]; snprintf(buf, sizeof(buf), "%s", argv[++i]);
|
||||||
@@ -143,12 +146,26 @@ int main(int argc, char **argv) {
|
|||||||
msg = argv[++i];
|
msg = argv[++i];
|
||||||
} else if (strcmp(argv[i], "-tls1_2") == 0) {
|
} else if (strcmp(argv[i], "-tls1_2") == 0) {
|
||||||
only_12 = 1;
|
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; }
|
if (!host) { fprintf(stderr, "need -connect host:port\n"); return 1; }
|
||||||
|
|
||||||
paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_CLIENT);
|
paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_CLIENT);
|
||||||
if (only_12) paze_tls_config_set_versions(cfg, 0, 1);
|
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);
|
if (sni) paze_tls_config_set_sni(cfg, sni);
|
||||||
const char *alpn_default[] = { "http/1.1" };
|
const char *alpn_default[] = { "http/1.1" };
|
||||||
paze_tls_config_set_alpn(cfg, alpn_default, 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_bio_t bio = { &sock, sock_read, sock_write };
|
||||||
paze_tls_set_bio(tls, bio);
|
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;
|
paze_status_t st;
|
||||||
for (int retry = 0; retry < 100; retry++) {
|
for (int retry = 0; retry < 100; retry++) {
|
||||||
st = paze_tls_handshake(tls);
|
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);
|
paze_tls_free(tls); closesocket(sock); paze_tls_config_free(cfg);
|
||||||
return 1;
|
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_version(tls),
|
||||||
(unsigned)paze_tls_get_cipher_suite(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) {
|
if (sess_out) {
|
||||||
paze_tls_session_t ss;
|
paze_tls_session_t ss;
|
||||||
if (paze_tls_get_session(tls, &ss) == PAZE_OK && ss.valid) {
|
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];
|
uint8_t buf[16384];
|
||||||
if (msg) {
|
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));
|
int n = paze_tls_read(tls, buf, sizeof(buf));
|
||||||
if (n > 0) { fwrite(buf, 1, (size_t)n, stdout); fprintf(stderr, "\n"); }
|
if (n > 0) { fwrite(buf, 1, (size_t)n, stdout); fprintf(stderr, "\n"); }
|
||||||
} else {
|
} else {
|
||||||
@@ -347,7 +374,7 @@ int main(int argc, char **argv) {
|
|||||||
if (strcmp(cmd, "s_server") == 0) {
|
if (strcmp(cmd, "s_server") == 0) {
|
||||||
int port = 4433;
|
int port = 4433;
|
||||||
const char *certf = NULL; const char *keyf = NULL;
|
const char *certf = NULL; const char *keyf = NULL;
|
||||||
int only_12 = 0;
|
int only_12 = 0, do_early = 0;
|
||||||
int reuse = 1;
|
int reuse = 1;
|
||||||
for (int i = 2; i < argc; i++) {
|
for (int i = 2; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "-accept") == 0 && i + 1 < argc) port = atoi(argv[++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], "-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], "-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], "-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) {
|
if (!certf || !keyf) {
|
||||||
fprintf(stderr, "s_server needs -cert and -key\n");
|
fprintf(stderr, "s_server needs -cert and -key\n");
|
||||||
@@ -370,6 +399,7 @@ int main(int argc, char **argv) {
|
|||||||
/* Setup TLS config */
|
/* Setup TLS config */
|
||||||
paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_SERVER);
|
paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_SERVER);
|
||||||
if (only_12) paze_tls_config_set_versions(cfg, 0, 1);
|
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_cert_chain(cfg, (const char *)cdata, (const char *)kdata);
|
||||||
paze_tls_config_set_verify(cfg, PAZE_TLS_VERIFY_NONE, NULL, NULL);
|
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) {
|
if (st != PAZE_OK) {
|
||||||
fprintf(stderr, "TLS handshake failed: %s\n", paze_tls_last_error(tls));
|
fprintf(stderr, "TLS handshake failed: %s\n", paze_tls_last_error(tls));
|
||||||
} else {
|
} 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_version(tls),
|
||||||
(unsigned)paze_tls_get_cipher_suite(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,使后续连接可恢复会话 */
|
/* 跨连接共享 session ticket key,使后续连接可恢复会话 */
|
||||||
uint8_t tk[32]; int tk_ready = 0;
|
uint8_t tk[32]; int tk_ready = 0;
|
||||||
paze_tls_get_session_ticket_key(tls, tk, &tk_ready);
|
paze_tls_get_session_ticket_key(tls, tk, &tk_ready);
|
||||||
|
|||||||
+25
-1
@@ -195,6 +195,13 @@ void paze_tls_config_free(paze_tls_config_t *cfg);
|
|||||||
/* 设置 SNI 主机名(client) */
|
/* 设置 SNI 主机名(client) */
|
||||||
paze_status_t paze_tls_config_set_sni(paze_tls_config_t *cfg, const char *host);
|
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 项) */
|
/* 设置 ALPN 协议列表(以 NULL 结尾的字符串数组,n 项) */
|
||||||
paze_status_t paze_tls_config_set_alpn(paze_tls_config_t *cfg, const char *const *alpn, int 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,
|
paze_status_t paze_tls_config_set_versions(paze_tls_config_t *cfg,
|
||||||
int enable_1_3, int enable_1_2);
|
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);
|
int paze_tls_is_handshake_done(const paze_tls_t *tls);
|
||||||
|
|
||||||
/* 应用层数据读写。read 返回读取字节数(>0);0=对端关闭;负数=错误。
|
/* 应用层数据读写。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_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);
|
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 */
|
/* 优雅关闭:发送 close_notify */
|
||||||
paze_status_t paze_tls_close(paze_tls_t *tls);
|
paze_status_t paze_tls_close(paze_tls_t *tls);
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,22 @@ paze_status_t paze_tls_config_set_alpn(paze_tls_config_t *cfg, const char *const
|
|||||||
return PAZE_OK;
|
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 序列)。
|
/* 由证书 PEM 文本构造证书链(CertificateEntry 序列)。
|
||||||
* 支持多张证书串联:依次拼接 [3字节长度前缀 + DER]。
|
* 支持多张证书串联:依次拼接 [3字节长度前缀 + DER]。
|
||||||
* 返回 malloc 的缓冲区,*outlen 为长度。 */
|
* 返回 malloc 的缓冲区,*outlen 为长度。 */
|
||||||
@@ -314,6 +330,8 @@ void paze_tls_free(paze_tls_t *tls) {
|
|||||||
paze_tls_config_free(tls->cfg_owned);
|
paze_tls_config_free(tls->cfg_owned);
|
||||||
free(tls->ch1);
|
free(tls->ch1);
|
||||||
free(tls->sh1);
|
free(tls->sh1);
|
||||||
|
free(tls->ch2);
|
||||||
|
free(tls->hrr_raw);
|
||||||
paze_memzero(tls, sizeof(*tls));
|
paze_memzero(tls, sizeof(*tls));
|
||||||
free(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) {
|
int paze_tls_read(paze_tls_t *tls, uint8_t *buf, size_t len) {
|
||||||
if (!tls || !buf || len == 0) return PAZE_ERR_INVALID;
|
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 中已有的未读明文 */
|
/* 先消费 app_buf 中已有的未读明文 */
|
||||||
if (tls->app_off < tls->app_len) {
|
if (tls->app_off < tls->app_len) {
|
||||||
size_t n = len < tls->app_len - tls->app_off ? len : tls->app_len - tls->app_off;
|
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;
|
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) {
|
paze_status_t paze_tls_close(paze_tls_t *tls) {
|
||||||
if (!tls) return PAZE_ERR_INVALID;
|
if (!tls) return PAZE_ERR_INVALID;
|
||||||
if (!tls->close_sent && tls->handshake_done) {
|
if (!tls->close_sent && tls->handshake_done) {
|
||||||
|
|||||||
+19
-2
@@ -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)。
|
/* HRR 后(only_group != 0):只发指定组的 share(RFC 8446 §4.1.4)。
|
||||||
* 否则按 kind 全量发送(首次 CH:尽可能避免 HRR 往返)。 */
|
* 否则按 kind 全量发送(首次 CH:尽可能避免 HRR 往返)。 */
|
||||||
if (only_group == PAZE_TLS_GROUP_X25519 ||
|
if (only_group == PAZE_TLS_GROUP_X25519 ||
|
||||||
(only_group == 0 && (kex->kind == PAZE_KEX_X25519 || kex->kind == PAZE_KEX_P256))) {
|
(only_group == 0 && kex->kind == PAZE_KEX_X25519)) {
|
||||||
/* X25519 share */
|
/* X25519 share(kind==P256 时该公钥未生成,不得写入) */
|
||||||
paze_store16_be(p, PAZE_TLS_GROUP_X25519); p += 2;
|
paze_store16_be(p, PAZE_TLS_GROUP_X25519); p += 2;
|
||||||
paze_store16_be(p, 32); p += 2;
|
paze_store16_be(p, 32); p += 2;
|
||||||
memcpy(p, kex->x25519_pub, 32); p += 32;
|
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;
|
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)
|
* server_name (SNI)
|
||||||
* list length(2) || list: name_type(1) || name_length(2) || name
|
* list length(2) || list: name_type(1) || name_length(2) || name
|
||||||
|
|||||||
@@ -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 paze_tls12_client_handshake(paze_tls_t *tls) {
|
||||||
paze_status_t st;
|
paze_status_t st;
|
||||||
|
tls->proto13 = 0; /* 1.3 回退到 1.2:record 层必须切回 1.2 分支 */
|
||||||
|
|
||||||
/* ---- 阶段 A:发送 ClientHello / 等待并解析 ServerHello ---- */
|
/* ---- 阶段 A:发送 ClientHello / 等待并解析 ServerHello ---- */
|
||||||
if (tls->hs_state == PAZE_HS_INIT) {
|
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_tls_config_t *cfg = tls->cfg_owned;
|
||||||
paze_status_t st;
|
paze_status_t st;
|
||||||
uint16_t client_suites[64]; size_t n_suites = 0;
|
uint16_t client_suites[64]; size_t n_suites = 0;
|
||||||
|
tls->proto13 = 0; /* 1.3 回退到 1.2:record 层必须切回 1.2 分支 */
|
||||||
|
|
||||||
/* ---- 阶段 A:解析 ClientHello、选择参数 ---- */
|
/* ---- 阶段 A:解析 ClientHello、选择参数 ---- */
|
||||||
if (tls->hs_state == PAZE_HS_INIT) {
|
if (tls->hs_state == PAZE_HS_INIT) {
|
||||||
|
|||||||
+439
-53
@@ -24,13 +24,33 @@ uint8_t *paze_tls_hs_put_header(uint8_t *msg, uint8_t type, size_t body_len) {
|
|||||||
return msg;
|
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));
|
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;
|
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_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;
|
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;
|
if (paze_random(kex->p256_priv, 32) != PAZE_OK) return PAZE_ERR_PLATFORM;
|
||||||
paze_bn_t d, n, tmp;
|
paze_bn_t d, n, tmp;
|
||||||
paze_p256_load_n(&n);
|
paze_p256_load_n(&n);
|
||||||
@@ -100,6 +120,17 @@ static paze_status_t recv_hs(paze_tls_t *tls,
|
|||||||
continue; /* 兼容性 CCS(1.2 互操作):忽略 */
|
continue; /* 兼容性 CCS(1.2 互操作):忽略 */
|
||||||
}
|
}
|
||||||
if (ct != PAZE_TLS_CT_HANDSHAKE) {
|
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) {
|
if (!tls->handshake_done) {
|
||||||
paze_tls_set_err(tls, "unexpected ct %d during hs", ct);
|
paze_tls_set_err(tls, "unexpected ct %d during hs", ct);
|
||||||
return PAZE_ERR_PROTOCOL;
|
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_alpn(p, cfg->alpn, cfg->alpn_count); p += n;
|
||||||
n = paze_tls_ext_write_renegotiation_info(p); 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(必须最后)。
|
/* PSK 会话恢复:psk_key_exchange_modes + pre_shared_key(必须最后)。
|
||||||
* pre_shared_key 分两段构造:binder 数据不参与 binder 的 transcript hash,
|
* pre_shared_key 分两段构造:binder 数据不参与 binder 的 transcript hash,
|
||||||
* 因此先写 identities + binders 总长度,计算 hash,再填 binder。 */
|
* 因此先写 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 计算点 */
|
size_t ch_partial = (size_t)(p - out); /* binder 计算点 */
|
||||||
uint8_t tr_h[64];
|
uint8_t tr_h[64];
|
||||||
if (tls->psk_hash == 1) paze_sha384(out, ch_partial, tr_h);
|
if (tls->had_hrr) {
|
||||||
else paze_sha256(out, ch_partial, tr_h);
|
/* 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];
|
uint8_t binder[64];
|
||||||
st = paze_tls13_compute_binder(tls, tr_h, hl, binder);
|
st = paze_tls13_compute_binder(tls, tr_h, hl, binder);
|
||||||
if (st != PAZE_OK) return st;
|
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;
|
return PAZE_ERR_VERIFY;
|
||||||
}
|
}
|
||||||
if (leaf->pk_kind == PAZE_X509_PK_ECDSA_P256) {
|
if (leaf->pk_kind == PAZE_X509_PK_ECDSA_P256) {
|
||||||
if (sig_alg != PAZE_TLS_SIG_ECDSA_SECP256R1_SHA256) {
|
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;
|
return PAZE_ERR_VERIFY;
|
||||||
}
|
|
||||||
/* TLS 1.3 的 ECDSA 签名为 DER 编码 (RFC 8446 §4.2.3) */
|
/* 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_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;
|
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} */
|
/* For CH parsing: {v, g, s, sni} */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int v;
|
int v;
|
||||||
uint16_t g;
|
uint16_t g;
|
||||||
int s;
|
int s;
|
||||||
char sni[256];
|
char sni[256];
|
||||||
|
int early; /* CH 携带 early_data 扩展 */
|
||||||
} ch_ext_ctx_t;
|
} ch_ext_ctx_t;
|
||||||
|
|
||||||
static int ch_ext_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) {
|
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);
|
s->g = paze_tls_ext_parse_supported_groups(b, l);
|
||||||
} else if (tp == PAZE_TLS_EXT_SERVER_NAME) {
|
} else if (tp == PAZE_TLS_EXT_SERVER_NAME) {
|
||||||
if (paze_tls_ext_parse_sni(b, l, s->sni)) s->s = 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -453,39 +520,188 @@ static void client_recv_new_session_ticket(paze_tls_t *tls) {
|
|||||||
tls->psk_len = plen;
|
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_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_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg;
|
||||||
paze_status_t st;
|
paze_status_t st;
|
||||||
|
|
||||||
if (tls->hs_state == PAZE_HS_INIT) {
|
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;
|
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;
|
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;
|
return PAZE_ERR_PROTOCOL;
|
||||||
}
|
}
|
||||||
if (blen < 2 + 32 + 1 + 2 + 1) return PAZE_ERR_FORMAT;
|
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;
|
const uint8_t *bp = body;
|
||||||
bp += 2 + 32; /* skip ver + random */
|
bp += 2 + 32; /* skip ver + random */
|
||||||
uint8_t sid_len = *bp++;
|
uint8_t sid_len = *bp++;
|
||||||
@@ -532,12 +757,11 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) {
|
|||||||
return PAZE_ERR_NOTSUP;
|
return PAZE_ERR_NOTSUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 若协商 hash 为 SHA-384,transcript 需用 SHA-384 从头重算:
|
/* 按最终协商 hash 重建 transcript(统一处理 SHA-384 与 HRR 情况):
|
||||||
* recv_hs 已把 CH+SH 算入 SHA-256 transcript,这里重建为 SHA-384(CH || SH) */
|
* recv_hs 已把 CH/SH 算入 SHA-256 transcript,这里从原始消息重建。 */
|
||||||
if (tls->neg.hash == paze_hash_sha384()) {
|
if (tls->had_hrr || tls->neg.hash == paze_hash_sha384()) {
|
||||||
paze_tls_tr_init(&tls->transcript, paze_hash_sha384());
|
st = client_rebuild_transcript(tls, sh_raw, sh_raw_len);
|
||||||
if (tls->ch1) paze_tls_tr_update(&tls->transcript, tls->ch1, tls->ch1_len);
|
if (st != PAZE_OK) return st;
|
||||||
if (sh_raw) paze_tls_tr_update(&tls->transcript, sh_raw, sh_raw_len);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse SH extensions */
|
/* 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);
|
memcpy(tls->neg.alpn, alpn_list[0], an);
|
||||||
tls->neg.alpn[an] = 0;
|
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;
|
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;
|
uint8_t vd[64]; size_t vd_len = 0;
|
||||||
st = build_finished(tls, 0, vd, &vd_len);
|
st = build_finished(tls, 0, vd, &vd_len);
|
||||||
if (st != PAZE_OK) return st;
|
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; }
|
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 长度字段) */
|
size_t partial_len = ch_raw_len - bl; /* CH 去掉 binder 列表(保留 binders 长度字段) */
|
||||||
uint8_t ph[64];
|
uint8_t ph[64];
|
||||||
if (tls->psk_hash == 1) paze_sha384(ch_raw, partial_len, ph);
|
if (tls->sent_hrr) {
|
||||||
else paze_sha256(ch_raw, partial_len, ph);
|
/* 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) {
|
if (paze_tls13_psk_early_secret(tls, tls->psk, tls->psk_len, tls->psk_hash) != PAZE_OK) {
|
||||||
PAZE_LOGD("psk: early secret failed");
|
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);
|
paze_tls_set_err(tls, "expected ClientHello, got %d", type);
|
||||||
return PAZE_ERR_PROTOCOL;
|
return PAZE_ERR_PROTOCOL;
|
||||||
}
|
}
|
||||||
/* 保存 ClientHello 原始消息(含 4 字节头):供协商 hash 重放 */
|
/* 保存 ClientHello 原始消息(含 4 字节头):供协商 hash 重放 / HRR 合成 */
|
||||||
if (!tls->ch1 && ch_raw && ch_raw_len > 0) {
|
if (!tls->ch1 && ch_raw && ch_raw_len > 0) {
|
||||||
tls->ch1 = (uint8_t *)malloc(ch_raw_len);
|
tls->ch1 = (uint8_t *)malloc(ch_raw_len);
|
||||||
if (!tls->ch1) return PAZE_ERR_PLATFORM;
|
if (!tls->ch1) return PAZE_ERR_PLATFORM;
|
||||||
memcpy(tls->ch1, ch_raw, ch_raw_len);
|
memcpy(tls->ch1, ch_raw, ch_raw_len);
|
||||||
tls->ch1_len = 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;
|
if (blen < 2 + 32 + 1) return PAZE_ERR_FORMAT;
|
||||||
const uint8_t *bp = body;
|
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 */
|
if (!chosen_cipher) return PAZE_ERR_NOTSUP; /* 无 1.3 套件 → 回退 1.2 */
|
||||||
tls->neg.cipher = chosen_cipher;
|
tls->neg.cipher = chosen_cipher;
|
||||||
tls->neg.version = PAZE_TLS_VERSION_1_3; /* 服务端协商为 1.3 */
|
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,
|
paze_tls_cipher_params(chosen_cipher, &tls->neg.aead, &tls->neg.hash,
|
||||||
&tls->neg.key_len, &tls->neg.hash_len);
|
&tls->neg.key_len, &tls->neg.hash_len);
|
||||||
/* 用协商 hash 重建 transcript;必须重放 ClientHello(recv_hs 已把它
|
/* 用协商 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,
|
* 成功则 psk_mode=1,ServerHello 回复 selected_identity,
|
||||||
* 握手跳过 Certificate/CertificateVerify。 */
|
* 握手跳过 Certificate/CertificateVerify。 */
|
||||||
tls->psk_mode = 0;
|
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;
|
tls->psk_mode = 1;
|
||||||
|
|
||||||
char sni_buf[PAZE_TLS_SNI_MAX + 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;
|
sni_buf[sn] = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Lookup key_share */
|
/* CH2(HRR 后)不得再携带 early_data(RFC 8446 §4.1.4) */
|
||||||
uint16_t select_group = sx.g;
|
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;
|
const uint8_t *ks_pub = NULL; size_t ks_publen = 0;
|
||||||
int have_share = 0;
|
int have_share = 0;
|
||||||
ks_lookup_ctx_t ksl = { select_group, &ks_pub, &ks_publen, &have_share };
|
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);
|
paze_tls_extensions_foreach(exts, 2 + ext_total, ks_lookup_cb, &ksl);
|
||||||
|
|
||||||
uint8_t shared[64]; size_t shared_len = 0;
|
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) {
|
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;
|
if (st != PAZE_OK) return st;
|
||||||
st = paze_tls_kex_ecdh(&tls->kex, select_group, ks_pub, ks_publen, shared, &shared_len);
|
st = paze_tls_kex_ecdh(&tls->kex, select_group, ks_pub, ks_publen, shared, &shared_len);
|
||||||
if (st != PAZE_OK) return st;
|
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) {
|
} else if (!tls->psk_mode) {
|
||||||
/* 无 key_share:仅当纯 PSK 恢复(psk_ke)才允许 */
|
/* HRR 后仍未提供 key_share → 非法 */
|
||||||
paze_tls_set_err(tls, "CH missing key share for group %d", select_group);
|
paze_tls_set_err(tls, "CH2 missing key share for group %d", select_group);
|
||||||
return PAZE_ERR_PROTOCOL;
|
return PAZE_ERR_PROTOCOL;
|
||||||
}
|
}
|
||||||
/* psk_ke:shared_len 保持 0,handshake_secret IKM 用 0 */
|
/* 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 */
|
/* Build ServerHello */
|
||||||
uint8_t sh[PAZE_TLS_MAX_RECORD];
|
uint8_t sh[PAZE_TLS_MAX_RECORD];
|
||||||
uint8_t *sp = sh;
|
uint8_t *sp = sh;
|
||||||
@@ -1003,6 +1368,11 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) {
|
|||||||
paze_memzero(shared, shared_len);
|
paze_memzero(shared, shared_len);
|
||||||
if (st != PAZE_OK) return st;
|
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;
|
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) {
|
if (cfg->alpn_count > 0) {
|
||||||
en = paze_tls_ext_write_alpn(ep, cfg->alpn, 1); ep += en;
|
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));
|
size_t extl = (size_t)(ep - (ext_total_ptr + 2));
|
||||||
paze_store16_be(ext_total_ptr, (uint16_t)extl);
|
paze_store16_be(ext_total_ptr, (uint16_t)extl);
|
||||||
size_t body_len = (size_t)(ep - (ee + 4));
|
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);
|
st = send_hs(tls, PAZE_TLS_HS_FINISHED, vd, vd_len);
|
||||||
if (st != PAZE_OK) return st;
|
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;
|
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 (st != PAZE_OK) return st;
|
||||||
if (type == PAZE_TLS_HS_CERTIFICATE) continue;
|
if (type == PAZE_TLS_HS_CERTIFICATE) continue;
|
||||||
if (type == PAZE_TLS_HS_CERTIFICATE_VERIFY) 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) {
|
if (type != PAZE_TLS_HS_FINISHED) {
|
||||||
paze_tls_set_err(tls, "expected client Finished, got %d", type);
|
paze_tls_set_err(tls, "expected client Finished, got %d", type);
|
||||||
return PAZE_ERR_PROTOCOL;
|
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);
|
st = verify_finished(tls, 0, body, blen);
|
||||||
if (st != PAZE_OK) { paze_tls_set_err(tls, "client Finished verify failed"); return PAZE_ERR_VERIFY; }
|
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 使用。
|
||||||
* 再把 client Finished 计入 transcript,供 resumption_master_secret 使用。 */
|
* (app keys 已在发完 server Finished 后派生,不受影响。) */
|
||||||
st = paze_tls13_derive_app_keys(tls);
|
|
||||||
if (st != PAZE_OK) return st;
|
|
||||||
if (raw && raw_len) paze_tls_tr_update(&tls->transcript, raw, raw_len);
|
if (raw && raw_len) paze_tls_tr_update(&tls->transcript, raw, raw_len);
|
||||||
if (paze_tls13_derive_resumption_master(tls) != PAZE_OK) {
|
if (paze_tls13_derive_resumption_master(tls) != PAZE_OK) {
|
||||||
paze_tls_set_err(tls, "derive resumption master failed");
|
paze_tls_set_err(tls, "derive resumption master failed");
|
||||||
|
|||||||
+24
-3
@@ -43,6 +43,12 @@
|
|||||||
#define PAZE_TLS_MAX_ALPN 8
|
#define PAZE_TLS_MAX_ALPN 8
|
||||||
#define PAZE_TLS_SNI_MAX 255
|
#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 种类 */
|
/* AEAD 种类 */
|
||||||
typedef enum {
|
typedef enum {
|
||||||
PAZE_AEAD_NONE = 0,
|
PAZE_AEAD_NONE = 0,
|
||||||
@@ -132,6 +138,9 @@ struct paze_tls_config {
|
|||||||
|
|
||||||
/* 0-RTT: 客户端启用(服务端默认接受,1=接受) */
|
/* 0-RTT: 客户端启用(服务端默认接受,1=接受) */
|
||||||
int enable_early;
|
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 client_finished_key[64];
|
||||||
uint8_t server_finished_key[64];
|
uint8_t server_finished_key[64];
|
||||||
uint8_t binder_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];
|
uint8_t resumption_master_secret[64];
|
||||||
int resumption_master_valid;
|
int resumption_master_valid;
|
||||||
|
|
||||||
@@ -260,6 +270,7 @@ struct paze_tls {
|
|||||||
int early_sent; /* 客户端已发送 0-RTT 数据 */
|
int early_sent; /* 客户端已发送 0-RTT 数据 */
|
||||||
int early_accepted; /* 服务端 EE 含 early_data(客户端视角) */
|
int early_accepted; /* 服务端 EE 含 early_data(客户端视角) */
|
||||||
int early_accept; /* 服务端接受 0-RTT(服务端视角) */
|
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_read;
|
||||||
paze_tls_traffic_key_t early_write;
|
paze_tls_traffic_key_t early_write;
|
||||||
int early_read_valid;
|
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:
|
/* 0-RTT early traffic key:
|
||||||
* client: 由 early_secret + hash(CH) 派生 "c e traffic" → early_write
|
* client: 由 early_secret + hash(CH) 派生 "c e traffic" → early_write
|
||||||
* server: 同上 → early_read
|
* server: 同上 → early_read
|
||||||
* is_client 决定写入哪个方向;aead 由调用方从协商/首选套件决定。 */
|
* is_client 决定写入哪个方向;aead 为 0-RTT 使用的 AEAD(客户端 = CH 首选套件的
|
||||||
paze_status_t paze_tls13_derive_early_key(paze_tls_t *tls, int is_client);
|
* 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 对象 */
|
/* 由 traffic secret 派生 traffic key 对象 */
|
||||||
paze_status_t paze_tls13_make_traffic_key(paze_tls_traffic_key_t *tk,
|
paze_status_t paze_tls13_make_traffic_key(paze_tls_traffic_key_t *tk,
|
||||||
const paze_hash_alg_t *alg,
|
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) */
|
/* 服务端:构造 key_share 扩展(单 group) */
|
||||||
size_t paze_tls_ext_write_key_share_server(uint8_t *buf, uint16_t group,
|
size_t paze_tls_ext_write_key_share_server(uint8_t *buf, uint16_t group,
|
||||||
const uint8_t *pub, size_t publen);
|
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 */
|
/* psk_key_exchange_modes(客户端): psk_ke + psk_dhe_ke */
|
||||||
size_t paze_tls_ext_write_psk_key_exchange_modes(uint8_t *buf);
|
size_t paze_tls_ext_write_psk_key_exchange_modes(uint8_t *buf);
|
||||||
/* pre_shared_key(客户端): identities(1) + binder(1)。返回写入字节数。 */
|
/* 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);
|
uint8_t *paze_tls_hs_put_header(uint8_t *msg, uint8_t type, size_t body_len);
|
||||||
|
|
||||||
/* kex 生成 + ECDHE 共享计算 */
|
/* 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,
|
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,
|
const uint8_t *peer_pub, size_t peer_pub_len,
|
||||||
uint8_t *shared_out, size_t *shared_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 调用) */
|
/* 1.3 握手 (由 tls_client/tls_server 调用) */
|
||||||
paze_status_t paze_tls13_client_handshake(paze_tls_t *tls);
|
paze_status_t paze_tls13_client_handshake(paze_tls_t *tls);
|
||||||
paze_status_t paze_tls13_server_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 独立模式复用。 */
|
/* 构造兼容 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);
|
paze_status_t paze_tls13_build_ch(paze_tls_t *tls, uint8_t *out, size_t *outlen);
|
||||||
/* TLS 1.2 握手 */
|
/* TLS 1.2 握手 */
|
||||||
|
|||||||
+18
-10
@@ -22,6 +22,7 @@
|
|||||||
* TLS 1.2 PRF (RFC 5246 §5): P_hash(secret, seed) 分块 HMAC,拼接至 outlen。
|
* TLS 1.2 PRF (RFC 5246 §5): P_hash(secret, seed) 分块 HMAC,拼接至 outlen。
|
||||||
*/
|
*/
|
||||||
#include "tls_internal.h"
|
#include "tls_internal.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
/* ---- cipher suite -> 参数 ---- */
|
/* ---- cipher suite -> 参数 ---- */
|
||||||
void paze_tls_cipher_params(uint16_t cipher,
|
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;
|
size_t hl = H->digest_len;
|
||||||
uint8_t zero[64]; memset(zero, 0, sizeof(zero));
|
uint8_t zero[64]; memset(zero, 0, sizeof(zero));
|
||||||
paze_hkdf_extract(H, zero, hl, psk, psk_len, tls->early_secret);
|
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):
|
/* 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。
|
* → client 侧为 early_write;server 侧为 early_read。
|
||||||
* hash 用 PSK 关联 hash;transcript 取"最终 ClientHello"的哈希(直接用原始字节算,
|
* hash 用 PSK 关联 hash;transcript 取"最终 ClientHello"的哈希(直接用原始字节算,
|
||||||
* 因为 early_secret 的 hash 可能不同于 transcript 对象的协商 hash)。
|
* 因为 early_secret 的 hash 可能不同于 transcript 对象的协商 hash)。
|
||||||
* aead 由调用方经 tls->neg.aead(已协商)或客户端首选套件决定。 */
|
* 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_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);
|
const paze_hash_alg_t *H = psk_hash_of(tls->psk_hash);
|
||||||
size_t hl = H->digest_len;
|
size_t hl = H->digest_len;
|
||||||
const uint8_t *ch = tls->ch1 ? tls->ch1 : NULL;
|
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);
|
if (H->digest_len == 48) paze_sha384(ch, chl, th);
|
||||||
else paze_sha256(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,
|
paze_status_t st = paze_tls_hkdf_expand_label(H, tls->early_secret, hl,
|
||||||
"c e traffic", th, hl, c_e, hl);
|
"c e traffic", th, hl, c_e, hl);
|
||||||
if (st != PAZE_OK) return st;
|
if (st != PAZE_OK) return st;
|
||||||
/* 客户端在收到 ServerHello 前派生:aead 用 CH 首选套件(1301 → AES-128-GCM) */
|
if (aead == PAZE_AEAD_NONE) aead = PAZE_AEAD_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;
|
|
||||||
st = paze_tls13_make_traffic_key(is_client ? &tls->early_write : &tls->early_read,
|
st = paze_tls13_make_traffic_key(is_client ? &tls->early_write : &tls->early_read,
|
||||||
H, c_e, hl, aead);
|
H, c_e, hl, aead);
|
||||||
paze_memzero(c_e, hl);
|
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;
|
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,
|
paze_status_t paze_tls13_compute_binder(paze_tls_t *tls,
|
||||||
const uint8_t *tr_hash, size_t tr_hash_len,
|
const uint8_t *tr_hash, size_t tr_hash_len,
|
||||||
uint8_t *binder_out) {
|
uint8_t *binder_out) {
|
||||||
const paze_hash_alg_t *H = psk_hash_of(tls->psk_hash);
|
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,
|
return paze_hmac(H, tls->binder_finished_key, H->digest_len,
|
||||||
binder_out);
|
tr_hash, tr_hash_len, binder_out);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* resumption_master_secret = Derive-Secret(master_secret, "res master", transcript)
|
/* resumption_master_secret = Derive-Secret(master_secret, "res master", transcript)
|
||||||
|
|||||||
+12
-4
@@ -23,6 +23,7 @@
|
|||||||
* 缓冲: in_buf 维护已读未解析字节流;每次 record_read 消费一条完整 record。
|
* 缓冲: in_buf 维护已读未解析字节流;每次 record_read 消费一条完整 record。
|
||||||
*/
|
*/
|
||||||
#include "tls_internal.h"
|
#include "tls_internal.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
/* 1.3 nonce: iv XOR (0^4 || seq_be8) */
|
/* 1.3 nonce: iv XOR (0^4 || seq_be8) */
|
||||||
static void nonce13(uint8_t nonce[12], const uint8_t iv[12], uint64_t seq) {
|
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;
|
return PAZE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 明文 record(握手初期 / Alert) */
|
/* 明文 record(握手初期 / Alert)。1.3 下加密 record 的外层类型一律
|
||||||
if (!tls->enc_read || !tls->read_key || tls->neg.version == 0) {
|
* 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;
|
*content_type = rtype;
|
||||||
*out = body;
|
*out = body;
|
||||||
*outlen = rlen;
|
*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,
|
paze_status_t paze_tls_record_send(paze_tls_t *tls, uint8_t content_type,
|
||||||
const uint8_t *frag, size_t len) {
|
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);
|
return paze_tls_record_send_plain(tls, content_type, frag, len);
|
||||||
|
|
||||||
if (len > PAZE_TLS_MAX_RECORD) return PAZE_ERR_OVERFLOW;
|
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;
|
uint8_t *o = tls->out_buf;
|
||||||
size_t off = 0;
|
size_t off = 0;
|
||||||
|
|
||||||
if (tls->neg.version == PAZE_TLS_VERSION_1_3) {
|
if (tls->proto13) {
|
||||||
/* plaintext = frag || content_type */
|
/* plaintext = frag || content_type */
|
||||||
uint8_t pt[PAZE_TLS_MAX_RECORD + 1];
|
uint8_t pt[PAZE_TLS_MAX_RECORD + 1];
|
||||||
memcpy(pt, frag, len);
|
memcpy(pt, frag, len);
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户