From bdf98f9d7bf05fe07b9a110e2680246f22a64301 Mon Sep 17 00:00:00 2001 From: JGZYES Date: Thu, 13 Aug 2026 22:13:55 +0800 Subject: [PATCH] =?UTF-8?q?HRR/0-RTT=20=E5=9F=BA=E7=A1=80=E8=AE=BE?= =?UTF-8?q?=E6=96=BD:=20=E5=86=85=E9=83=A8=E5=AD=97=E6=AE=B5=E3=80=81early?= =?UTF-8?q?=5Fdata=20=E6=89=A9=E5=B1=95=E3=80=81key=5Fshare=20=E5=8D=95?= =?UTF-8?q?=E7=BB=84=E6=A8=A1=E5=BC=8F=E4=B8=8E=20early=20traffic=20key=20?= =?UTF-8?q?=E6=B4=BE=E7=94=9F;=20=E4=BF=AE=E5=A4=8D=20key=5Fshare=5Fclient?= =?UTF-8?q?=20=E5=8F=82=E6=95=B0=E4=B8=8D=E5=8C=B9=E9=85=8D=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E9=94=99=E8=AF=AF;=20STATUS=20=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E8=A1=A8=E6=94=B9=E4=B8=BA=E7=8B=AC=E7=AB=8B=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- STATUS.md | 16 ++++++++-------- src/ssl/tls_extensions.c | 40 +++++++++++++++++++++++++++++---------- src/ssl/tls_handshake13.c | 2 +- src/ssl/tls_internal.h | 33 +++++++++++++++++++++++++++++++- src/ssl/tls_keys.c | 37 ++++++++++++++++++++++++++++++++++++ 5 files changed, 108 insertions(+), 20 deletions(-) diff --git a/STATUS.md b/STATUS.md index fa55431..f0b8ef5 100644 --- a/STATUS.md +++ b/STATUS.md @@ -58,14 +58,14 @@ | OpenSSH | PazeSSH | 说明 | |---|---|---| -| `ssh` | `pssh` / `pssh ssh` | 远程 shell / 执行命令 / 端口转发 | -| `scp` | `pssh scp` | SFTP 优先 + legacy SCP 回退 | -| `sftp` | `pssh sshd` 子系统 | 服务端 SFTP v3,互操作通过 | -| `ssh-keygen` | `pssh keygen` | ed25519/rsa/ecdsa | -| `ssh-keyscan` | `pssh keyscan` | known_hosts 格式 | -| `ssh-copy-id` | `pssh copy-id` | 安装公钥 | -| `ssh-agent` / `ssh-add` | `pssh agent` / `add` | agent 密钥管理 | -| `sshd` | `pssh sshd` | 多用户 + 权限 + 公钥认证 + SFTP | +| `ssh` | `pssh` | 远程 shell / 执行命令 / 端口转发 | +| `scp` | `pscp` | SFTP 优先 + legacy SCP 回退 | +| `sftp` | `psftp` | 独立 SFTP 客户端;服务端为 `psshd` 子系统 | +| `ssh-keygen` | `pkeygen` | ed25519/rsa/ecdsa | +| `ssh-keyscan` | `pkeyscan` | known_hosts 格式 | +| `ssh-copy-id` | `pcopy-id` | 安装公钥 | +| `ssh-agent` / `ssh-add` | `pagent` / `padd` | agent 密钥管理 | +| `sshd` | `psshd` | 多用户 + 权限 + 公钥认证 + SFTP | ## 遗留项(已知) diff --git a/src/ssl/tls_extensions.c b/src/ssl/tls_extensions.c index ac13c10..3d736f3 100644 --- a/src/ssl/tls_extensions.c +++ b/src/ssl/tls_extensions.c @@ -120,7 +120,8 @@ size_t paze_tls_ext_write_signature_algorithms(uint8_t *buf) { /* ============================================================ * key_share (client: 多 share; server: 单 share; HelloRetryRequest: 单 selected_group) * ============================================================ */ -size_t paze_tls_ext_write_key_share_client(uint8_t *buf, const paze_tls_kex_t *kex) { +size_t paze_tls_ext_write_key_share_client(uint8_t *buf, const paze_tls_kex_t *kex, + uint16_t only_group) { uint8_t *p = buf + 4; uint8_t *s = p; size_t bodylen = 0; @@ -128,19 +129,23 @@ size_t paze_tls_ext_write_key_share_client(uint8_t *buf, const paze_tls_kex_t *k if (kex->kind == PAZE_KEX_NONE) return 0; /* 至少 1 个 share */ - /* X25519 share */ - if (kex->kind == PAZE_KEX_X25519 || kex->kind == PAZE_KEX_P256) { - /* 先写 X25519 */ + /* HRR 后(only_group != 0):只发指定组的 share(RFC 8446 §4.1.4)。 + * 否则按 kind 全量发送(首次 CH:尽可能避免 HRR 往返)。 */ + if (only_group == PAZE_TLS_GROUP_X25519 || + (only_group == 0 && (kex->kind == PAZE_KEX_X25519 || kex->kind == PAZE_KEX_P256))) { + /* X25519 share */ paze_store16_be(p, PAZE_TLS_GROUP_X25519); p += 2; paze_store16_be(p, 32); p += 2; memcpy(p, kex->x25519_pub, 32); p += 32; - /* 如存在 P-256,也写上(备选) */ - if (kex->kind == PAZE_KEX_P256) { - paze_store16_be(p, PAZE_TLS_GROUP_SECP256R1); p += 2; - paze_store16_be(p, 65); p += 2; - memcpy(p, kex->p256_pub, 65); p += 65; - } } + if (only_group == PAZE_TLS_GROUP_SECP256R1 || + (only_group == 0 && kex->kind == PAZE_KEX_P256)) { + /* P-256 share(仅 kind==P256 或 HRR 选定组时) */ + paze_store16_be(p, PAZE_TLS_GROUP_SECP256R1); p += 2; + paze_store16_be(p, 65); p += 2; + memcpy(p, kex->p256_pub, 65); p += 65; + } + if (p == s + 2) return 0; /* 无 share 可写 */ bodylen = (size_t)(p - s); paze_store16_be(s, (uint16_t)(bodylen - 2)); /* client_shares 长度 */ return put_ext_header(buf, PAZE_TLS_EXT_KEY_SHARE, bodylen) + bodylen; @@ -279,6 +284,21 @@ size_t paze_tls_ext_write_renegotiation_info(uint8_t *buf) { return put_ext_header(buf, PAZE_TLS_EXT_RENEGOTIATION_INFO, body) + body; } +/* ============================================================ + * early_data (RFC 8446 §4.2.10) + * ClientHello: 空 body(声明客户端支持 0-RTT) + * EncryptedExtensions: 空 body(服务端接受 0-RTT) + * ============================================================ */ +size_t paze_tls_ext_write_early_data(uint8_t *buf) { + return put_ext_header(buf, PAZE_TLS_EXT_EARLY_DATA, 0); +} + +/* 解析 early_data 扩展:存在即返回 1(body 应为空,不校验) */ +int paze_tls_ext_parse_early_data(const uint8_t *data, size_t len) { + (void)data; (void)len; + return 1; +} + /* ============================================================ * PSK 会话恢复扩展 (RFC 8446 §4.2.11) * ============================================================ */ diff --git a/src/ssl/tls_handshake13.c b/src/ssl/tls_handshake13.c index 02bb8bc..fbac332 100644 --- a/src/ssl/tls_handshake13.c +++ b/src/ssl/tls_handshake13.c @@ -162,7 +162,7 @@ paze_status_t paze_tls13_build_ch(paze_tls_t *tls, uint8_t *out, size_t *outlen) n = paze_tls_ext_write_supported_versions(p, 1, cfg->enable_1_3, cfg->enable_1_2); p += n; n = paze_tls_ext_write_supported_groups(p); p += n; n = paze_tls_ext_write_signature_algorithms(p); p += n; - n = paze_tls_ext_write_key_share_client(p, &tls->kex); + n = paze_tls_ext_write_key_share_client(p, &tls->kex, tls->had_hrr ? tls->hrr_group : 0); if (n == 0) return PAZE_ERR_INVALID; p += n; n = paze_tls_ext_write_sni(p, cfg->sni); p += n; diff --git a/src/ssl/tls_internal.h b/src/ssl/tls_internal.h index 496d81b..60ca599 100644 --- a/src/ssl/tls_internal.h +++ b/src/ssl/tls_internal.h @@ -129,6 +129,9 @@ struct paze_tls_config { * 即可实现跨连接会话恢复。 */ uint8_t ticket_key[32]; int ticket_key_ready; + + /* 0-RTT: 客户端启用(服务端默认接受,1=接受) */ + int enable_early; }; /* ============================================================ @@ -245,6 +248,26 @@ struct paze_tls { uint8_t *ch1; size_t ch1_len; int hello_retry; + /* HRR(HelloRetryRequest,RFC 8446 §4.1.3/§4.1.4) */ + uint8_t *hrr_raw; size_t hrr_raw_len; /* 服务端发送的 / 客户端收到的 HRR 原始 */ + uint8_t *ch2; size_t ch2_len; /* 客户端 HRR 后发送的 ClientHello2 原始 */ + int had_hrr; /* 本次握手是否发生 HRR */ + uint16_t hrr_group; /* HRR 选择的组 */ + int sent_hrr; /* 服务端已发送 HRR(等待 CH2) */ + + /* 0-RTT 状态(RFC 8446 §4.2.10/§4.5) */ + int early_offer; /* 客户端 CH 携带 early_data 扩展 */ + int early_sent; /* 客户端已发送 0-RTT 数据 */ + int early_accepted; /* 服务端 EE 含 early_data(客户端视角) */ + int early_accept; /* 服务端接受 0-RTT(服务端视角) */ + paze_tls_traffic_key_t early_read; + paze_tls_traffic_key_t early_write; + int early_read_valid; + int early_write_valid; + uint8_t early_buf[2048]; /* 服务端收到的 0-RTT 数据(握手完成后交应用) */ + size_t early_len; + size_t early_off; + /* 客户端:TLS 1.2 回退时保存的 ServerHello 原始消息 */ uint8_t *sh1; size_t sh1_len; @@ -328,6 +351,11 @@ paze_status_t paze_tls_derive_secret(const paze_hash_alg_t *alg, paze_status_t paze_tls13_derive_handshake_keys(paze_tls_t *tls, const uint8_t *shared, size_t shared_len); paze_status_t paze_tls13_derive_app_keys(paze_tls_t *tls); +/* 0-RTT early traffic key: + * client: 由 early_secret + hash(CH) 派生 "c e traffic" → early_write + * server: 同上 → early_read + * is_client 决定写入哪个方向;aead 由调用方从协商/首选套件决定。 */ +paze_status_t paze_tls13_derive_early_key(paze_tls_t *tls, int is_client); /* 由 traffic secret 派生 traffic key 对象 */ paze_status_t paze_tls13_make_traffic_key(paze_tls_traffic_key_t *tk, const paze_hash_alg_t *alg, @@ -367,10 +395,13 @@ paze_status_t paze_tls12_prf(const paze_hash_alg_t *alg, size_t paze_tls_ext_write_supported_versions(uint8_t *buf, int is_client, int v13, int v12); size_t paze_tls_ext_write_supported_groups(uint8_t *buf); size_t paze_tls_ext_write_signature_algorithms(uint8_t *buf); -size_t paze_tls_ext_write_key_share_client(uint8_t *buf, const paze_tls_kex_t *kex); +size_t paze_tls_ext_write_key_share_client(uint8_t *buf, const paze_tls_kex_t *kex, + uint16_t only_group); size_t paze_tls_ext_write_sni(uint8_t *buf, const char *host); size_t paze_tls_ext_write_alpn(uint8_t *buf, char *const *alpn, int n); size_t paze_tls_ext_write_renegotiation_info(uint8_t *buf); +size_t paze_tls_ext_write_early_data(uint8_t *buf); +int paze_tls_ext_parse_early_data(const uint8_t *data, size_t len); /* 服务端:构造 key_share 扩展(单 group) */ size_t paze_tls_ext_write_key_share_server(uint8_t *buf, uint16_t group, diff --git a/src/ssl/tls_keys.c b/src/ssl/tls_keys.c index 6236dba..9d0959f 100644 --- a/src/ssl/tls_keys.c +++ b/src/ssl/tls_keys.c @@ -243,6 +243,43 @@ paze_status_t paze_tls13_psk_early_secret(paze_tls_t *tls, return paze_tls_derive_secret(H, tls->early_secret, hl, "res binder", NULL, tls->binder_key); } +/* 0-RTT early traffic key (RFC 8446 §7.1): + * client_early_traffic_secret = Derive-Secret(early_secret, "c e traffic", hash(CH)) + * → client 侧为 early_write;server 侧为 early_read。 + * hash 用 PSK 关联 hash;transcript 取"最终 ClientHello"的哈希(直接用原始字节算, + * 因为 early_secret 的 hash 可能不同于 transcript 对象的协商 hash)。 + * aead 由调用方经 tls->neg.aead(已协商)或客户端首选套件决定。 */ +paze_status_t paze_tls13_derive_early_key(paze_tls_t *tls, int is_client) { + const paze_hash_alg_t *H = psk_hash_of(tls->psk_hash); + size_t hl = H->digest_len; + const uint8_t *ch = tls->ch1 ? tls->ch1 : NULL; + size_t chl = tls->ch1 ? tls->ch1_len : 0; + /* HRR 后:early key 基于最终 ClientHello(本实现 HRR 时客户端放弃 0-RTT, + * 服务端收到 CH2 时 ch1 仍指向 CH1;此处客户端不会调用)。 */ + if (tls->had_hrr && tls->ch2 && is_client == 0) { ch = tls->ch2; chl = tls->ch2_len; } + if (!ch || chl == 0) return PAZE_ERR_PROTOCOL; + + uint8_t th[64]; + if (H->digest_len == 48) paze_sha384(ch, chl, th); + else paze_sha256(ch, chl, th); + + uint8_t c_e[64]; + paze_status_t st = paze_tls_hkdf_expand_label(H, tls->early_secret, hl, + "c e traffic", th, hl, c_e, hl); + if (st != PAZE_OK) return st; + /* 客户端在收到 ServerHello 前派生:aead 用 CH 首选套件(1301 → AES-128-GCM) */ + paze_tls_aead_kind_t aead = tls->neg.aead; + if (is_client && aead == PAZE_AEAD_NONE) aead = PAZE_AEAD_AES_128_GCM; + st = paze_tls13_make_traffic_key(is_client ? &tls->early_write : &tls->early_read, + H, c_e, hl, aead); + paze_memzero(c_e, hl); + if (st == PAZE_OK) { + if (is_client) tls->early_write_valid = 1; + else tls->early_read_valid = 1; + } + return st; +} + /* binder = HMAC(binder_key, transcript_hash); 输出长度 = H->digest_len */ paze_status_t paze_tls13_compute_binder(paze_tls_t *tls, const uint8_t *tr_hash, size_t tr_hash_len,