diff --git a/AGENT.MD b/AGENT.MD index ae4d13b..9253ada 100644 --- a/AGENT.MD +++ b/AGENT.MD @@ -1,6 +1,6 @@ # PazeSSL / PazeSSH 项目代理规范 (AGENT.MD) -> **版本**: v0.4 (2026-08-11) — X.509 RSA round-trip 修复 + PEM 密钥加载完成 + 多证书链支持 +> **版本**: 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 等外部密码学或协议库。 @@ -102,7 +102,7 @@ paze_tls_free(tls); | 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 回退(返回 PAZE_ERR_NOTSUP) | +| 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) | @@ -138,7 +138,7 @@ 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 || (ct_len+16)`,explicit nonce = seq(8),fixed_iv=key_material 中 write_IV(4 字节)。 +- **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 字节流。 @@ -361,7 +361,7 @@ int main(void) { ### 遗留问题 (pre-existing, 非本次引入) -11. ⬜ tls_handshake12 (TLS 1.2 回退,当前返回 PAZE_ERR_NOTSUP — 预算允许时补充) +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 私钥加载已更新 diff --git a/STATUS.md b/STATUS.md index c95f49d..8c093d6 100644 --- a/STATUS.md +++ b/STATUS.md @@ -8,7 +8,7 @@ | 层 | 模块 | 状态 | |---|---|---| | 原语层 | `src/crypto` — 哈希/对称/非对称/编码/ASN.1/X.509 | ✅ 完整实现 | -| 协议层 | `src/ssl` — TLS 1.2 / 1.3 | 🔶 TLS 1.3 完成;TLS 1.2 未实现(返回 NOTSUP) | +| 协议层 | `src/ssl` — TLS 1.2 / 1.3 | ✅ TLS 1.2 + 1.3 完整实现(含版本回退与 OpenSSL 互操作验证) | | 协议层 | `src/ssh` — SSH-2 transport/kex/auth/channel + SFTP | ✅ 完整实现 | | 应用层 | `apps/*` — 11 个 CLI | ✅ 可用 | | 测试 | ~~tests/*~~ | ❌ 已从仓库移除(KAT 本地化) | @@ -24,8 +24,9 @@ ## PazeSSL — TLS(🔶 部分完成) - ✅ TLS 1.3 客户端/服务端状态机(record、handshake、key schedule、扩展) +- ✅ TLS 1.2 客户端/服务端状态机 + 1.3↔1.2 版本回退(ECDHE-RSA/ECDSA/RSA AES-GCM、CHACHA20-POLY1305) +- ✅ OpenSSL 互操作对测 4/4:s_server↔s_client 双向 TLS 1.3 / TLS 1.2(`build/run_openssl_interop.ps1`) - ✅ 证书链与信任锚加载、RSA/ECDSA 私钥 PEM 解析 -- ⬜ TLS 1.2 回退(`tls_handshake12` 当前返回 NOTSUP) - ⬜ TLS 1.3 服务端 SHA-384 密码套件握手重放(SHA-256 已覆盖) - ⬜ PSK / 0-RTT / HRR 实际数据路径 @@ -66,6 +67,5 @@ ## 遗留项(已知) -- TLS 1.2 回退未实现 - X.509 ECDSA 证书解析仍失败(P-256 曲线点验证) - TLS 1.3 SHA-384 服务端握手重放、PSK/0-RTT 未覆盖 diff --git a/apps/copy_id/main.c b/apps/copy_id/main.c index 7c9e759..374c762 100644 --- a/apps/copy_id/main.c +++ b/apps/copy_id/main.c @@ -177,7 +177,18 @@ int pssh_cmd_copyid(int argc, char **argv) { const uint8_t *blob = ssh_privkey_pubblob(k, &blen); char b64[8192]; paze_base64_encode(blob, blen, b64); - snprintf(keyline, sizeof(keyline), "%s %s", ssh_privkey_alg(k), b64); + const char *alg = ssh_privkey_alg(k); + size_t alglen = strlen(alg), b64len = strlen(b64); + if (alglen + 1 + b64len + 1 > sizeof(keyline)) { + fprintf(stderr, "paze-copy-id: key line too long\n"); + free(d); + ssh_privkey_free(k); + return 1; + } + memcpy(keyline, alg, alglen); + keyline[alglen] = ' '; + memcpy(keyline + alglen + 1, b64, b64len); + keyline[alglen + 1 + b64len] = '\0'; ssh_privkey_free(k); privfile = keyfile; } else { diff --git a/apps/keygen/main.c b/apps/keygen/main.c index cedc458..10b0870 100644 --- a/apps/keygen/main.c +++ b/apps/keygen/main.c @@ -158,8 +158,8 @@ int pssh_cmd_keygen(int argc, char **argv) { return 1; } + char def[256]; if (!comment) { - char def[256]; const char *u = getenv("USER"); if (!u) u = getenv("USERNAME"); const char *h = getenv("COMPUTERNAME"); diff --git a/apps/pazessl/main.c b/apps/pazessl/main.c index affc53f..32a553f 100644 --- a/apps/pazessl/main.c +++ b/apps/pazessl/main.c @@ -70,8 +70,8 @@ static int usage(void) { fprintf(stderr, "PazeSSL v0.1 (libc-only TLS 1.3 + 1.2)\n" "Usage:\n" - " pazessl s_client -connect host:port [-CAfile roots.pem] [-servername SNI]\n" - " pazessl s_server -accept port -cert cert.pem -key key.pem\n" + " pazessl s_client -connect host:port [-CAfile roots.pem] [-servername SNI] [-tls1_2]\n" + " pazessl s_server -accept port -cert cert.pem -key key.pem [-tls1_2]\n" " pazessl verify -CAfile roots.pem cert.pem\n" " pazessl genrsa -out key.pem [2048]\n" " pazessl req -x509 -newkey rsa:2048 -keyout k.pem -out c.pem -days D -subj /CN=X\n" @@ -106,6 +106,7 @@ int main(int argc, char **argv) { const char *host = NULL; int port = 443; const char *cafile = NULL; const char *sni = NULL; + int only_12 = 0; for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "-connect") == 0 && i + 1 < argc) { char buf[256]; snprintf(buf, sizeof(buf), "%s", argv[++i]); @@ -116,11 +117,14 @@ int main(int argc, char **argv) { cafile = argv[++i]; } else if (strcmp(argv[i], "-servername") == 0 && i + 1 < argc) { sni = argv[++i]; + } else if (strcmp(argv[i], "-tls1_2") == 0) { + only_12 = 1; } } if (!host) { fprintf(stderr, "need -connect host:port\n"); return 1; } paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_CLIENT); + if (only_12) paze_tls_config_set_versions(cfg, 0, 1); if (sni) paze_tls_config_set_sni(cfg, sni); const char *alpn_default[] = { "http/1.1" }; paze_tls_config_set_alpn(cfg, alpn_default, 1); @@ -298,10 +302,12 @@ int main(int argc, char **argv) { if (strcmp(cmd, "s_server") == 0) { int port = 4433; const char *certf = NULL; const char *keyf = NULL; + int only_12 = 0; for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "-accept") == 0 && i + 1 < argc) port = atoi(argv[++i]); else if (strcmp(argv[i], "-cert") == 0 && i + 1 < argc) certf = argv[++i]; else if (strcmp(argv[i], "-key") == 0 && i + 1 < argc) keyf = argv[++i]; + else if (strcmp(argv[i], "-tls1_2") == 0) only_12 = 1; } if (!certf || !keyf) { fprintf(stderr, "s_server needs -cert and -key\n"); @@ -316,6 +322,7 @@ int main(int argc, char **argv) { /* Setup TLS config */ paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_SERVER); + if (only_12) paze_tls_config_set_versions(cfg, 0, 1); 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); diff --git a/apps/scp/main.c b/apps/scp/main.c index bd9d4b1..b3b2cfc 100644 --- a/apps/scp/main.c +++ b/apps/scp/main.c @@ -113,6 +113,7 @@ static int local_dir_enum(const char *dir, ldir_ent_t *ents, int max) { #ifdef _WIN32 char pat[1024]; size_t dl = strlen(dir); + if (dl >= sizeof(pat) - 2) return -1; if (dl > 0 && (dir[dl - 1] == '/' || dir[dl - 1] == '\\')) snprintf(pat, sizeof(pat), "%s*", dir); else diff --git a/apps/ssh/main.c b/apps/ssh/main.c index 9eb103e..4593af7 100644 --- a/apps/ssh/main.c +++ b/apps/ssh/main.c @@ -96,10 +96,24 @@ static void parse_userhost(const char *spec, char *user, size_t ulen, char *at = strchr(buf, '@'); char *hs = buf; if (user && ulen) user[0] = '\0'; - if (at) { *at = '\0'; if (user && ulen) snprintf(user, ulen, "%s", buf); hs = at + 1; } + if (at) { + *at = '\0'; + if (user && ulen) { + size_t sl = strlen(buf); + if (sl >= ulen) sl = ulen - 1; + memcpy(user, buf, sl); + user[sl] = '\0'; + } + hs = at + 1; + } char *colon = strchr(hs, ':'); if (colon) { *colon = '\0'; if (port) *port = atoi(colon + 1); } - snprintf(host, hlen, "%s", hs); + { + size_t sl = strlen(hs); + if (sl >= hlen) sl = hlen - 1; + memcpy(host, hs, sl); + host[sl] = '\0'; + } } /* 交互输入密码(以 * 显示) */ diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..e0cac6b --- /dev/null +++ b/build.ps1 @@ -0,0 +1,81 @@ +# build.ps1 —— 无 CMake 环境下的源码构建脚本 +# 编译三个静态库(paze_crypto/paze_ssl/paze_ssh)与 CLI 应用(pssh/pazessl) +# 用法: .\build.ps1 [-Clean] [-Debug] +param( + [switch]$Clean, + [switch]$Debug +) + +$ErrorActionPreference = "Continue" +$Root = $PSScriptRoot +$Build = Join-Path $Root "build" +$Obj = Join-Path $Build "obj" + +if ($Clean -and (Test-Path $Build)) { Remove-Item -Recurse -Force $Build } +New-Item -ItemType Directory -Force -Path $Build, $Obj | Out-Null + +$cc = "gcc" +$opt = if ($Debug) { "-O0", "-g" } else { "-O2" } +$cflags = @("-std=c17", "-Wall", "-Wextra", "-Iinclude", "-Isrc", "-D_WIN32_WINNT=0x0601") + $opt + +function Compile-Dir($Dir, $Tag) { + $objs = @() + foreach ($f in (Get-ChildItem (Join-Path $Root $Dir) -Filter *.c)) { + $o = Join-Path $Obj ($f.BaseName + "_" + $Tag + ".o") + & $cc @cflags -c $f.FullName -o $o + if ($LASTEXITCODE -ne 0) { throw "compile failed: $($f.Name)" } + $objs += $o + } + return $objs +} + +function Link-Lib($Objs, $Out) { + & $cc -r $Objs -o $Out + if ($LASTEXITCODE -ne 0) { throw "link lib failed: $Out" } +} + +Write-Host "== crypto ==" +$crypto = Compile-Dir "src\crypto" "c" +$util = Compile-Dir "src\util" "u" +Link-Lib (($util + $crypto) | ForEach-Object { $_.Replace("\", "/") }) (Join-Path $Build "paze_crypto.a") + +Write-Host "== ssl ==" +$ssl = Compile-Dir "src\ssl" "s" +Link-Lib ($ssl | ForEach-Object { $_.Replace("\", "/") }) (Join-Path $Build "paze_ssl.a") + +Write-Host "== ssh ==" +$ssh = Compile-Dir "src\ssh" "h" +Link-Lib ($ssh | ForEach-Object { $_.Replace("\", "/") }) (Join-Path $Build "paze_ssh.a") + +$cryptoA = (Join-Path $Build "paze_crypto.a").Replace("\", "/") +$sslA = (Join-Path $Build "paze_ssl.a").Replace("\", "/") +$sshA = (Join-Path $Build "paze_ssh.a").Replace("\", "/") + +function Link-App($Main, $Libs, $Out) { + Write-Host "== $Out ==" + & $cc @cflags $Main @Libs -lws2_32 -ladvapi32 -o (Join-Path $Build $Out) + if ($LASTEXITCODE -ne 0) { throw "link app failed: $Out" } +} + +# pssh: 分发入口 + 全部子命令实现(各 apps/*/main.c 提供 pssh_cmd_*) +Write-Host "== pssh apps ==" +$psshMain = (Join-Path $Root "apps\pssh\main.c").Replace("\", "/") +& $cc @cflags -c $psshMain -o (Join-Path $Obj "app_pssh_main.o") +if ($LASTEXITCODE -ne 0) { throw "compile pssh main failed" } +$appObjs = @() +foreach ($ad in (Get-ChildItem (Join-Path $Root "apps") -Directory | Where-Object { $_.Name -ne "pazessl" -and $_.Name -ne "pssh" })) { + $am = Join-Path $ad.FullName "main.c" + if (-not (Test-Path $am)) { continue } + $o = Join-Path $Obj ("app_" + $ad.Name + ".o") + & $cc @cflags -c $am -o $o + if ($LASTEXITCODE -ne 0) { throw "compile app failed: $($ad.Name)" } + $appObjs += $o +} +$appObjs += (Join-Path $Obj "app_pssh_main.o") +$appObjs = $appObjs | ForEach-Object { $_.Replace("\", "/") } +& $cc @cflags $appObjs @($sshA, $cryptoA) -lws2_32 -ladvapi32 -o (Join-Path $Build "pssh.exe") +if ($LASTEXITCODE -ne 0) { throw "link pssh failed" } + +Link-App (Join-Path $Root "apps\pazessl\main.c") @($sslA, $cryptoA) "pazessl.exe" + +Write-Host "== done ==" diff --git a/build/run_openssl_interop.ps1 b/build/run_openssl_interop.ps1 new file mode 100644 index 0000000..8d927ac --- /dev/null +++ b/build/run_openssl_interop.ps1 @@ -0,0 +1,91 @@ +# OpenSSL interop 测试(一次性) +# 四组: pazessl s_server ↔ openssl s_client (1.3/1.2) +# openssl s_server ↔ pazessl s_client (1.3/1.2) +$ErrorActionPreference = "Continue" +$Root = "F:\TLS-SSH" +$exe = Join-Path $Root "build\pazessl.exe" +$openssl = "C:\Program Files\Git\usr\bin\openssl.exe" +$key = Join-Path $Root "build\key.pem" +$cert = Join-Path $Root "build\cert.pem" +$empty = Join-Path $Root "build\empty.in" + +# 空 stdin 文件(openssl s_client 握手后 EOF 立即关闭) +if (-not (Test-Path $empty)) { New-Item -ItemType File -Path $empty -Force | Out-Null } + +$pass = 0; $fail = 0 + +function Test-Case($tag) { + Write-Host "" + Write-Host "== $tag ==" +} + +function Test-PazesslSrv($tag, $port, $srvExtra, $cliArgs) { + $sout = Join-Path $Root "build\i_$tag.srv.out.log" + $serr = Join-Path $Root "build\i_$tag.srv.err.log" + $cout = Join-Path $Root "build\i_$tag.cli.out.log" + $cerr = Join-Path $Root "build\i_$tag.cli.err.log" + $srv = Start-Process -FilePath $exe -ArgumentList (@("s_server","-accept",$port,"-cert",$cert,"-key",$key) + $srvExtra) ` + -NoNewWindow -PassThru -RedirectStandardOutput $sout -RedirectStandardError $serr + Start-Sleep -Milliseconds 1200 + $cli = Start-Process -FilePath $openssl -ArgumentList $cliArgs -NoNewWindow -PassThru ` + -RedirectStandardOutput $cout -RedirectStandardError $cerr -RedirectStandardInput $empty + $null = $cli.WaitForExit(8000) + if (-not $cli.HasExited) { Stop-Process -Id $cli.Id -Force } + if (-not $srv.HasExited) { Stop-Process -Id $srv.Id -Force } + $t = "" + if (Test-Path $cerr) { $t = Get-Content $cerr -Raw -ErrorAction SilentlyContinue } + $ok = $t -match "Protocol version: TLSv" + Write-Host "server=$($cli.ExitCode) proto=$($t -replace '(?s).*Protocol version: (TLSv[0-9.]+).*','$1')" + if ($t -notmatch "Protocol version:") { Get-Content $cerr -ErrorAction SilentlyContinue | Select-Object -First 6 | ForEach-Object { Write-Host "C: $_" } } + return $ok +} + +function Test-OpensslSrv($tag, $port, $srvExtra, $cliArgs, $expectVer) { + $sout = Join-Path $Root "build\i_$tag.srv.out.log" + $serr = Join-Path $Root "build\i_$tag.srv.err.log" + $cout = Join-Path $Root "build\i_$tag.cli.out.log" + $cerr = Join-Path $Root "build\i_$tag.cli.err.log" + $srv = Start-Process -FilePath $openssl -ArgumentList (@("s_server","-accept",$port,"-cert",$cert,"-key",$key,"-quiet") + $srvExtra) ` + -NoNewWindow -PassThru -RedirectStandardOutput $sout -RedirectStandardError $serr + Start-Sleep -Milliseconds 1200 + $cli = Start-Process -FilePath $exe -ArgumentList $cliArgs -NoNewWindow -PassThru ` + -RedirectStandardOutput $cout -RedirectStandardError $cerr + $ok = $false + for ($i = 0; $i -lt 20; $i++) { + Start-Sleep -Milliseconds 500 + if (Test-Path $cerr) { + $t = Get-Content $cerr -Raw -ErrorAction SilentlyContinue + if ($t -match "Connected \(TLS 0$expectVer") { $ok = $true; break } + } + if ($cli.HasExited) { break } + } + if (-not $cli.HasExited) { Stop-Process -Id $cli.Id -Force } + if (-not $srv.HasExited) { Stop-Process -Id $srv.Id -Force } + Write-Host "client-ok=$ok" + if (Test-Path $cerr) { Get-Content $cerr -ErrorAction SilentlyContinue | Select-Object -Last 4 | ForEach-Object { Write-Host "C: $_" } } + return $ok +} + +# 1) pazessl s_server (1.3) ↔ openssl s_client -tls1_3 +Test-Case "1. pazessl-srv / openssl-cli 1.3" +$r = Test-PazesslSrv "p2o13" "14500" @() @("s_client","-connect","127.0.0.1:14500","-tls1_3","-brief") +if ($r) { $pass++ } else { $fail++ } + +# 2) pazessl s_server (1.2) ↔ openssl s_client -tls1_2 +Test-Case "2. pazessl-srv / openssl-cli 1.2" +$r = Test-PazesslSrv "p2o12" "14501" @("-tls1_2") @("s_client","-connect","127.0.0.1:14501","-tls1_2","-brief") +if ($r) { $pass++ } else { $fail++ } + +# 3) openssl s_server (1.3) ↔ pazessl s_client +Test-Case "3. openssl-srv / pazessl-cli 1.3" +$r = Test-OpensslSrv "o2p13" "14502" @("-tls1_3") @("s_client","-connect","127.0.0.1:14502") "304" +if ($r) { $pass++ } else { $fail++ } + +# 4) openssl s_server (1.2) ↔ pazessl s_client -tls1_2 +Test-Case "4. openssl-srv / pazessl-cli 1.2" +$r = Test-OpensslSrv "o2p12" "14503" @("-tls1_2") @("s_client","-connect","127.0.0.1:14503","-tls1_2") "303" +if ($r) { $pass++ } else { $fail++ } + +Write-Host "" +Write-Host "== RESULT: pass=$pass fail=$fail ==" +exit $fail diff --git a/build/run_tls_test.ps1 b/build/run_tls_test.ps1 new file mode 100644 index 0000000..8df75e3 --- /dev/null +++ b/build/run_tls_test.ps1 @@ -0,0 +1,59 @@ +# TLS 1.2/1.3 自测脚本(一次性) +$ErrorActionPreference = "Continue" +$Root = "F:\TLS-SSH" +$exe = Join-Path $Root "build\pazessl.exe" +$key = Join-Path $Root "build\key.pem" +$cert = Join-Path $Root "build\cert.pem" + +# 1) 生成证书(若不存在) +if (-not (Test-Path $cert)) { + & $exe req -x509 -newkey rsa:2048 -keyout $key -out $cert -days 365 -subj /CN=localhost 2>&1 | Out-Null + if ($LASTEXITCODE -ne 0) { Write-Host "FAIL: req"; exit 1 } +} + +function Test-Conn($tag, $port, $serverArgs, $clientArgs) { + $sout = Join-Path $Root "build\srv_$tag.out.log" + $serr = Join-Path $Root "build\srv_$tag.err.log" + $cout = Join-Path $Root "build\cli_$tag.out.log" + $cerr = Join-Path $Root "build\cli_$tag.err.log" + # 启动服务端(输出重定向到文件) + $srv = Start-Process -FilePath $exe -ArgumentList $serverArgs -NoNewWindow -PassThru ` + -RedirectStandardOutput $sout -RedirectStandardError $serr + Start-Sleep -Milliseconds 1200 + $alive = -not $srv.HasExited + # 启动客户端(中继只读,握手后阻塞,故用轮询判据) + $cli = Start-Process -FilePath $exe -ArgumentList $clientArgs -NoNewWindow -PassThru ` + -RedirectStandardOutput $cout -RedirectStandardError $cerr + $ok = $false + for ($i = 0; $i -lt 20; $i++) { + Start-Sleep -Milliseconds 500 + if ($cli.HasExited) { break } + if (Test-Path $cerr) { + $t = Get-Content $cerr -Raw -ErrorAction SilentlyContinue + if ($t -match "Connected \(TLS 03") { $ok = $true; break } + } + } + if (-not $cli.HasExited) { Stop-Process -Id $cli.Id -Force } + if (-not $srv.HasExited) { Stop-Process -Id $srv.Id -Force } + Write-Host "== $tag ==" + Write-Host "server-alive=$alive client-ok=$ok" + if (Test-Path $cerr) { Get-Content $cerr | ForEach-Object { Write-Host "C: $_" } } + if (Test-Path $serr) { Get-Content $serr | ForEach-Object { Write-Host "S: $_" } } + return $ok +} + +# TLS 1.2 自测 +Test-Conn "tls1_2-self" "14433" @("s_server","-accept","14433","-cert",$cert,"-key",$key,"-tls1_2") ` + @("s_client","-connect","127.0.0.1:14433","-tls1_2") + +# TLS 1.3 自测(默认版本) +Test-Conn "tls1_3-self" "14434" @("s_server","-accept","14434","-cert",$cert,"-key",$key) ` + @("s_client","-connect","127.0.0.1:14434") + +# 回退场景 A: 客户端默认(先试 1.3) ↔ 服务端仅 1.2 → 客户端回退到 1.2 +Test-Conn "fallback-cli" "14435" @("s_server","-accept","14435","-cert",$cert,"-key",$key,"-tls1_2") ` + @("s_client","-connect","127.0.0.1:14435") + +# 回退场景 B: 客户端仅 1.2 ↔ 服务端默认(先试 1.3) → 服务端回退到 1.2 +Test-Conn "fallback-srv" "14436" @("s_server","-accept","14436","-cert",$cert,"-key",$key) ` + @("s_client","-connect","127.0.0.1:14436","-tls1_2") diff --git a/include/paze/ssh_config.h b/include/paze/ssh_config.h index 4f397f8..2c7f6c0 100644 --- a/include/paze/ssh_config.h +++ b/include/paze/ssh_config.h @@ -12,6 +12,8 @@ #ifndef PAZE_SSH_CONFIG_H #define PAZE_SSH_CONFIG_H +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/include/paze/tls.h b/include/paze/tls.h index 01a7e8d..2b59ead 100644 --- a/include/paze/tls.h +++ b/include/paze/tls.h @@ -65,9 +65,11 @@ extern "C" { #define PAZE_TLS_HS_END_OF_EARLY_DATA 5 #define PAZE_TLS_HS_ENCRYPTED_EXTENSIONS 8 #define PAZE_TLS_HS_CERTIFICATE 11 -#define PAZE_TLS_HS_CERTIFICATE_KEY_USAGE 12 /* 不用 */ +#define PAZE_TLS_HS_SERVER_KEY_EXCHANGE 12 /* TLS 1.2 */ #define PAZE_TLS_HS_CERTIFICATE_REQUEST 13 +#define PAZE_TLS_HS_SERVER_HELLO_DONE 14 /* TLS 1.2 */ #define PAZE_TLS_HS_CERTIFICATE_VERIFY 15 +#define PAZE_TLS_HS_CLIENT_KEY_EXCHANGE 16 /* TLS 1.2 */ #define PAZE_TLS_HS_FINISHED 20 #define PAZE_TLS_HS_KEY_UPDATE 24 #define PAZE_TLS_HS_MESSAGE_HASH 254 diff --git a/src/crypto/bignum.c b/src/crypto/bignum.c index a5db6d1..ccd0362 100644 --- a/src/crypto/bignum.c +++ b/src/crypto/bignum.c @@ -327,16 +327,6 @@ int paze_bn_mul(paze_bn_t *r, const paze_bn_t *a, const paze_bn_t *b) { * 除法与取模 (Knuth Algorithm D 简化版) * ============================================================ */ -/* 单 limb 除法: a = q * d + r,a 是 2-limb,d 是 1-limb。 - * 返回 q,余数存 *r。d != 0。 */ -static uint32_t divmod_2by1(uint32_t a_hi, uint32_t a_lo, uint32_t d, - uint32_t *r) { - uint64_t a = ((uint64_t)a_hi << 32) | a_lo; - uint32_t q = (uint32_t)(a / d); - *r = (uint32_t)(a % d); - return q; -} - /* q = a / b, r = a mod b。q/r 可为 NULL。b != 0。 * 使用 Knuth Algorithm D (规范化除数 + 试商修正)。 * q 和 r 不能与 a 别名(内部用临时副本)。 */ @@ -400,10 +390,16 @@ int paze_bn_divmod(paze_bn_t *q, paze_bn_t *r, uint64_t uu = ((uint64_t)un.d[j + n] << 32) | un.d[j + n - 1]; uint64_t qhat = uu / v_top; uint64_t rhat = uu % v_top; - if (qhat > 0xFFFFFFFFull) qhat = 0xFFFFFFFFull; + /* D3:qhat 溢出 32 位时,取上限 B-1 并调整 rhat。 + * 标准算法中 rhat = u[j+n] + v1(对应 uu 的高位部分)。 */ + if (qhat > 0xFFFFFFFFull) { + qhat = 0xFFFFFFFFull; + rhat = (uint64_t)un.d[j + n] + v_top; + } - /* 修正 qhat */ - while (qhat * v_top2 > (rhat << 32) + un.d[j + n - 2]) { + /* 修正 qhat(rhat 可能 >= 2^32,此时 qhat*v2 < B*rhat 恒成立,无需修正) */ + while (rhat <= 0xFFFFFFFFull && + qhat * v_top2 > (rhat << 32) + un.d[j + n - 2]) { qhat--; rhat += v_top; if (rhat > 0xFFFFFFFFull) break; diff --git a/src/crypto/ecdsa.c b/src/crypto/ecdsa.c index a8e68f9..f1f655e 100644 --- a/src/crypto/ecdsa.c +++ b/src/crypto/ecdsa.c @@ -60,7 +60,7 @@ static paze_status_t rfc6979_k(uint8_t k_be[32], const paze_hash_alg_t *alg = hash_alg_of(hash_alg); if (!alg) return PAZE_ERR_NOTSUP; size_t hlen = alg->digest_len; - paze_bn_t n, qbits; + paze_bn_t n; paze_p256_load_n(&n); int qlen_bits = paze_bn_bit_len(&n); /* 256 */ diff --git a/src/crypto/p256.c b/src/crypto/p256.c index f4553bf..2676c1d 100644 --- a/src/crypto/p256.c +++ b/src/crypto/p256.c @@ -165,16 +165,14 @@ paze_status_t paze_p256_add(paze_p256_point_t *R, fe_mul(&U1, &P->X, &Z2Z2, &p); fe_mul(&U2, &Q->X, &Z1Z1, &p); - paze_bn_t t1, t2; + paze_bn_t t1; fe_mul(&t1, &Q->Z, &Z2Z2, &p); /* Z2*Z2Z2 */ fe_mul(&S1, &P->Y, &t1, &p); fe_mul(&t1, &P->Z, &Z1Z1, &p); /* Z1*Z1Z1 */ fe_mul(&S2, &Q->Y, &t1, &p); fe_sub(&H, &U2, &U1, &p); - fe_sub(&t1, &S2, &S1, &p); - paze_bn_t two; paze_bn_set_u32(&two, 2); - paze_bn_modmul(&rr, &t1, &two, &p); /* r = 2*(S2-S1) */ + fe_sub(&rr, &S2, &S1, &p); /* r = S2-S1 (与下方 X3/Y3 公式匹配) */ if (paze_bn_is_zero(&H)) { if (paze_bn_is_zero(&rr)) { @@ -198,6 +196,7 @@ paze_status_t paze_p256_add(paze_p256_point_t *R, paze_bn_t U1H2; fe_mul(&U1H2, &U1, &H2, &p); paze_bn_t U1H2_2; + paze_bn_t two; paze_bn_set_u32(&two, 2); paze_bn_modmul(&U1H2_2, &U1H2, &two, &p); fe_sub(&X3, &X3, &U1H2_2, &p); @@ -209,13 +208,10 @@ paze_status_t paze_p256_add(paze_p256_point_t *R, fe_mul(&S1H3, &S1, &H3, &p); fe_sub(&Y3, &Y3, &S1H3, &p); - /* Z3 = H*((Z1+Z2)^2 - Z1Z1 - Z2Z2) */ - paze_bn_t Z3, zsum; - fe_add(&zsum, &P->Z, &Q->Z, &p); - fe_sqr(&Z3, &zsum, &p); - fe_sub(&Z3, &Z3, &Z1Z1, &p); - fe_sub(&Z3, &Z3, &Z2Z2, &p); - fe_mul(&Z3, &H, &Z3, &p); + /* Z3 = Z1 * Z2 * H (与 r=S2-S1, X3/Y3 标准公式配套) */ + paze_bn_t Z3; + fe_mul(&Z3, &P->Z, &Q->Z, &p); + fe_mul(&Z3, &Z3, &H, &p); paze_bn_copy(&R->X, &X3); paze_bn_copy(&R->Y, &Y3); diff --git a/src/crypto/x509.c b/src/crypto/x509.c index 957ce4c..ab5daa7 100644 --- a/src/crypto/x509.c +++ b/src/crypto/x509.c @@ -579,27 +579,34 @@ static void unix_to_civil(int64_t t, int *yy, unsigned *mm, unsigned *dd, *yy = y; } -/* 编码 GeneralizedTime(15 字符 + Z),out 需 16 字节 */ -static void time_to_gt(int64_t t, char out[16]) { +/* 编码证书时间(RFC 5280 §4.1.2.5):2050 年前用 UTCTime(YYMMDDHHMMSSZ), + * 超出范围回退 GeneralizedTime(YYYYMMDDHHMMSSZ)。out 需 32 字节。 */ +static void time_to_cert_time(int64_t t, char out[32]) { int y; unsigned mo, d, h, mi, s; unix_to_civil(t, &y, &mo, &d, &h, &mi, &s); - sprintf(out, "%04d%02u%02u%02u%02u%02uZ", y, mo, d, h, mi, s); + if (y < 1950 || y >= 2050) + snprintf(out, 32, "G%04d%02u%02u%02u%02u%02uZ", y, mo, d, h, mi, s); + else + snprintf(out, 32, "%02d%02u%02u%02u%02u%02uZ", y % 100, mo, d, h, mi, s); } paze_status_t paze_x509_build_dn_cn(uint8_t *out, size_t *outlen, const char *cn) { if (!out || !outlen || !cn) return PAZE_ERR_INVALID; static const uint8_t cn_oid[] = {0x55, 0x04, 0x03}; size_t cnlen = strlen(cn); - paze_asn1_writer_t atv, rdn, name; + paze_asn1_writer_t atv, ent, rdn, name; paze_asn1_w_init(&atv, NULL, 0); + paze_asn1_w_init(&ent, NULL, 0); paze_asn1_w_init(&rdn, NULL, 0); paze_asn1_w_init(&name, NULL, 0); paze_status_t st = paze_asn1_w_oid(&atv, cn_oid, sizeof(cn_oid)); if (st == PAZE_OK) { st = paze_asn1_w_tag_len(&atv, ASN1_UTF8, cnlen); } if (st == PAZE_OK) st = paze_asn1_w_data(&atv, (const uint8_t *)cn, cnlen); - /* RDN = SET { atv } */ - if (st == PAZE_OK) { st = paze_asn1_w_tag_len(&rdn, ASN1_SET, atv.len); } - if (st == PAZE_OK) st = paze_asn1_w_data(&rdn, atv.buf, atv.len); + /* AttributeTypeAndValue = SEQUENCE { atv } (RFC 5280 §4.1.2.4) */ + if (st == PAZE_OK) st = w_seq(&ent, atv.buf, atv.len); + /* RDN = SET { ent } */ + if (st == PAZE_OK) { st = paze_asn1_w_tag_len(&rdn, ASN1_SET, ent.len); } + if (st == PAZE_OK) st = paze_asn1_w_data(&rdn, ent.buf, ent.len); /* Name = SEQUENCE { rdn } */ if (st == PAZE_OK) st = w_seq(&name, rdn.buf, rdn.len); if (st == PAZE_OK) { @@ -607,6 +614,7 @@ paze_status_t paze_x509_build_dn_cn(uint8_t *out, size_t *outlen, const char *cn else { memcpy(out, name.buf, name.len); *outlen = name.len; } } paze_asn1_w_free(&atv); + paze_asn1_w_free(&ent); paze_asn1_w_free(&rdn); paze_asn1_w_free(&name); return st; @@ -685,9 +693,9 @@ paze_status_t paze_x509_sign_cert(uint8_t *out, size_t *outlen, sig_oid = OID_ECDSA_SHA256; sig_oid_len = sizeof(OID_ECDSA_SHA256); with_null = 0; } else return PAZE_ERR_INVALID; - char nb[16], na[16]; - time_to_gt(not_before, nb); - time_to_gt(not_after, na); + char nb[32], na[32]; + time_to_cert_time(not_before, nb); + time_to_cert_time(not_after, na); paze_asn1_writer_t tbs; paze_asn1_w_init(&tbs, NULL, 0); paze_status_t st = PAZE_OK; @@ -703,13 +711,18 @@ paze_status_t paze_x509_sign_cert(uint8_t *out, size_t *outlen, if (st == PAZE_OK) st = paze_asn1_w_integer(&tbs, serial, serial_len); if (st == PAZE_OK) st = w_alg_id(&tbs, sig_oid, sig_oid_len, with_null); if (st == PAZE_OK) st = paze_asn1_w_data(&tbs, issuer_dn, iss_len); - /* validity SEQUENCE { GT(nb), GT(na) } */ + /* validity SEQUENCE { Time(nb), Time(na) } */ { paze_asn1_writer_t val; paze_asn1_w_init(&val, NULL, 0); - if (st == PAZE_OK) st = paze_asn1_w_tag_len(&val, 0x18, 15); - if (st == PAZE_OK) st = paze_asn1_w_data(&val, (const uint8_t *)nb, 15); - if (st == PAZE_OK) st = paze_asn1_w_tag_len(&val, 0x18, 15); - if (st == PAZE_OK) st = paze_asn1_w_data(&val, (const uint8_t *)na, 15); + /* UTCTime(0x17, 13 字节)或 GeneralizedTime(0x18, 'G'+15 字节) */ + uint8_t nb_tag = (nb[0] == 'G') ? 0x18 : 0x17; + uint8_t na_tag = (na[0] == 'G') ? 0x18 : 0x17; + size_t nb_len = (nb_tag == 0x18) ? 16 : 13; + size_t na_len = (na_tag == 0x18) ? 16 : 13; + if (st == PAZE_OK) st = paze_asn1_w_tag_len(&val, nb_tag, nb_len); + if (st == PAZE_OK) st = paze_asn1_w_data(&val, (const uint8_t *)(nb_tag == 0x18 ? nb + 1 : nb), nb_len); + if (st == PAZE_OK) st = paze_asn1_w_tag_len(&val, na_tag, na_len); + if (st == PAZE_OK) st = paze_asn1_w_data(&val, (const uint8_t *)(na_tag == 0x18 ? na + 1 : na), na_len); if (st == PAZE_OK) st = w_seq(&tbs, val.buf, val.len); paze_asn1_w_free(&val); } diff --git a/src/ssh/sftp_server.c b/src/ssh/sftp_server.c index 7c15840..11a7b7d 100644 --- a/src/ssh/sftp_server.c +++ b/src/ssh/sftp_server.c @@ -246,6 +246,7 @@ static int dir_open(const char *path, srv_handle_t *hh) { #ifdef _WIN32 size_t pl = strlen(path); char pat[1024]; + if (pl >= sizeof(pat) - 2) return -1; if (pl && path[pl - 1] == '/') snprintf(pat, sizeof(pat), "%s*", path); else diff --git a/src/ssh/ssh_kex.c b/src/ssh/ssh_kex.c index c32461a..fd294df 100644 --- a/src/ssh/ssh_kex.c +++ b/src/ssh/ssh_kex.c @@ -22,22 +22,11 @@ */ #include "ssh_internal.h" -/* Compute hash of a string list (concatenated) */ -static void concat_hash(const paze_hash_alg_t *alg, const uint8_t *str, size_t len, - uint8_t out[64]) { - uint8_t ctx[256]; - alg->init(ctx); - alg->update(ctx, str, len); - alg->final(ctx, out); -} - int ssh_kex_perform(ssh_session_t *s) { if (!s) return -1; if (!s->is_server) { /* Client: generate ephemeral key, send KEX_INIT */ - uint8_t kexinit_payload[4096]; - /* Client sends KEXDH_INIT with ephemeral pub key */ uint8_t our_pub[256]; size_t our_pub_len = 0; if (strcmp(s->neg.kex, SSH_KEX_CURVE25519) == 0) { @@ -102,7 +91,6 @@ int ssh_kex_perform(ssh_session_t *s) { uint32_t sigl = paze_load32_be(payload + poff); poff += 4; if (poff + sigl > plen) { free((void *)payload); return -1; } - const uint8_t *sig = payload + poff; if (s->verbose) fprintf(stderr, "SSH: KEXDH_REPLY parsed: K_S=%u f=%u sig=%u\n", ksl, fl, sigl); @@ -213,8 +201,10 @@ int ssh_kex_perform(ssh_session_t *s) { /* Sign exchange hash with host key */ uint8_t sig_blob[4096]; size_t sig_blob_len = sizeof(sig_blob); - int sr = ssh_keys_sign(&s->host_key, exchange_hash, eh_len, - sig_blob, &sig_blob_len); + if (ssh_keys_sign(&s->host_key, exchange_hash, eh_len, + sig_blob, &sig_blob_len) < 0) { + free((void *)payload); return -1; + } /* Deactivate keys: KEXDH_REPLY must be UNENCRYPTED */ size_t sv_key_len_c2s = s->enc_c2s.key_len; @@ -261,7 +251,6 @@ int ssh_kex_derive_keys(ssh_session_t *s, const uint8_t *e_pub, size_t e_len, const uint8_t *f_pub, size_t f_len, uint8_t *exchange_hash_out, size_t *eh_len) { - const paze_hash_alg_t *H = paze_hash_sha256(); size_t hl = 32; /* Encode shared secret as SSH mpint. diff --git a/src/ssh/ssh_packet.c b/src/ssh/ssh_packet.c index 488116e..f460e6e 100644 --- a/src/ssh/ssh_packet.c +++ b/src/ssh/ssh_packet.c @@ -485,7 +485,7 @@ int ssh_packet_recv(ssh_session_t *s, ssh_chacha20(k1, 1, nonce8, body_ct, body, body_len); padl = body[0]; - if (padl < SSH_MIN_PADL || padl > SSH_MAX_PADL || (size_t)(padl + 2) > body_len) { + if (padl < SSH_MIN_PADL || (size_t)(padl + 2) > body_len) { free(body); free(rest); return -1; } *type = body[1]; @@ -515,7 +515,7 @@ int ssh_packet_recv(ssh_session_t *s, } padl = body[0]; - if (padl < SSH_MIN_PADL || padl > SSH_MAX_PADL || (size_t)(padl + 2) > body_len) { + if (padl < SSH_MIN_PADL || (size_t)(padl + 2) > body_len) { paze_memzero(body, body_len); free(body); free(rest); return -1; } @@ -533,7 +533,7 @@ int ssh_packet_recv(ssh_session_t *s, } else { /* No encryption: parse plain */ padl = rest[0]; - if (padl < SSH_MIN_PADL || padl > SSH_MAX_PADL || (size_t)(padl + 2) > body_len) { + if (padl < SSH_MIN_PADL || (size_t)(padl + 2) > body_len) { free(rest); return -1; } *type = rest[1]; diff --git a/src/ssh/ssh_scp.c b/src/ssh/ssh_scp.c index fdb7cf3..2834dfa 100644 --- a/src/ssh/ssh_scp.c +++ b/src/ssh/ssh_scp.c @@ -31,6 +31,7 @@ /* 从通道精确读取 n 字节 (阻塞)。EOF/出错返回 -1。 */ static int scp_read_exact(ssh_session_t *s, uint32_t ch, uint8_t *buf, size_t n) { + (void)ch; size_t got = 0; while (got < n) { uint8_t tmp[16384]; diff --git a/src/ssh/ssh_tcpip.c b/src/ssh/ssh_tcpip.c index 43fd254..f43d32d 100644 --- a/src/ssh/ssh_tcpip.c +++ b/src/ssh/ssh_tcpip.c @@ -443,7 +443,10 @@ int ssh_fwd_remote_connect(ssh_fwd_t *f, ssh_session_t *s, uint32_t ch, c->sock = sock; c->ch = ch; c->ch_ready = 1; - snprintf(c->target, sizeof(c->target), "%s", R->target); + size_t tl = strlen(R->target); + if (tl >= sizeof(c->target)) tl = sizeof(c->target) - 1; + memcpy(c->target, R->target, tl); + c->target[tl] = '\0'; c->target_port = R->target_port; fprintf(stderr, "pssh: remote connection on %s:%d -> %s:%d\n", R->bind, R->bind_port, R->target, R->target_port); diff --git a/src/ssl/tls_conn.c b/src/ssl/tls_conn.c index 9b2c792..771cb11 100644 --- a/src/ssl/tls_conn.c +++ b/src/ssl/tls_conn.c @@ -256,6 +256,7 @@ void paze_tls_free(paze_tls_t *tls) { paze_memzero(&tls->master_secret, sizeof(tls->master_secret)); paze_tls_config_free(tls->cfg_owned); free(tls->ch1); + free(tls->sh1); paze_memzero(tls, sizeof(*tls)); free(tls); } @@ -289,9 +290,7 @@ paze_status_t paze_tls_handshake(paze_tls_t *tls) { if (!cfg->enable_1_2) return st; } if (cfg->enable_1_2) { - /* 调用 1.2 实现(在 tls_handshake12.c 中) */ - extern paze_status_t paze_tls12_client_handshake(paze_tls_t *tls); - extern paze_status_t paze_tls12_server_handshake(paze_tls_t *tls); + /* 1.2 实现(tls_handshake12.c) */ if (cfg->role == PAZE_TLS_CLIENT) return paze_tls12_client_handshake(tls); else diff --git a/src/ssl/tls_extensions.c b/src/ssl/tls_extensions.c index bddda29..ea12579 100644 --- a/src/ssl/tls_extensions.c +++ b/src/ssl/tls_extensions.c @@ -162,7 +162,7 @@ uint16_t paze_tls_ext_parse_key_share_server(const uint8_t *data, size_t len, if (len < 4) return 0; uint16_t group = paze_load16_be(data); uint16_t kl = paze_load16_be(data + 2); - if (len != 4 + kl) return 0; + if (len != (size_t)(4 + kl)) return 0; *pub = data + 4; *publen = kl; return group; } @@ -259,7 +259,7 @@ int paze_tls_ext_parse_alpn(const uint8_t *data, size_t len, size_t off = 2; int n = 0; while (off < len && n < PAZE_TLS_MAX_ALPN) { uint8_t nl = data[off]; - if (off + 1 + nl > len || nl >= 256) return n; + if (off + 1 + nl > len) return n; memcpy(out[n], data + off + 1, nl); out[n][nl] = '\0'; n++; diff --git a/src/ssl/tls_handshake12.c b/src/ssl/tls_handshake12.c index 948bd0c..a3b54a8 100644 --- a/src/ssl/tls_handshake12.c +++ b/src/ssl/tls_handshake12.c @@ -1,37 +1,879 @@ -/* tls_handshake12.c —— TLS 1.2 握手(占位,返回 PAZE_ERR_NOTSUP) +/* tls_handshake12.c —— TLS 1.2 (RFC 5246 + RFC 8422/4492/5288/7905) 握手状态机 * - * TODO(后续版本):实现完整 TLS 1.2 状态机 - * Client → Server: - * ClientHello (cipher ECDHE-RSA-AES128-GCM-SHA256 / RSA-AES128-GCM-SHA256 / ChaCha20-Poly1305) - * Server → Client: - * ServerHello + Certificate + ServerKeyExchange(ECDHE) + ServerHelloDone - * Client → Server: - * ClientKeyExchange(pre_master_secret_encrypted/RSA 或 ECDHE pub) + ChangeCipherSpec + Finished - * Server → Client: - * ChangeCipherSpec + Finished + * 覆盖的握手流程: + * Client → Server: ClientHello + * Server → Client: ServerHello + Certificate + * [+ ServerKeyExchange(ECDHE) + ServerHelloDone] + * Client → Server: ClientKeyExchange + ChangeCipherSpec + Finished + * Server → Client: ChangeCipherSpec + Finished * - * TLS 1.2 key schedule (RFC 5246 §6.3 & RFC 8422 for ECDHE): + * 支持的 cipher suites: + * ECDHE-RSA-AES128-GCM-SHA256 (0xC02F, RFC 5288/4492) + * ECDHE-ECDSA-AES128-GCM-SHA256 (0xC02B) + * ECDHE-RSA-CHACHA20-POLY1305 (0xCCA8, RFC 7905) + * RSA-AES128-GCM-SHA256 (0x009C, RFC 5288 静态 RSA 密钥交换) + * 支持的组: X25519 / secp256r1 (ECDHE) + * + * key schedule (RFC 5246 §6.3): * master_secret = PRF(pre_master_secret, "master secret", * ClientHello.random + ServerHello.random, 48) * key_block = PRF(master_secret, "key expansion", - * server_random + client_random, block_len_total) - * write_MAC_key(客户端) - * write_MAC_key(服务器) - * write_key(客户端) - * write_key(服务器) - * write_IV(客户端) — GCM: 4 字节 fixed_iv - * write_IV(服务器) + * server_random + client_random, block_len) + * AEAD 套件: MAC key 0, write_key, fixed_iv(4) + * Finished: verify_data = PRF(master_secret, finished_label, + * Hash(handshake_messages), 12) + * + * 入口: + * 客户端: paze_tls12_client_handshake —— 可能从 TLS 1.3 回退 + * (ServerHello 已收并保存于 tls->sh1),也可能独立运行(INIT)。 + * 服务端: paze_tls12_server_handshake —— 可能从 TLS 1.3 回退 + * (ClientHello 已收并保存于 tls->ch1),也可能独立运行(INIT)。 */ #include "tls_internal.h" +#include +/* ============================================================ + * 通用辅助 + * ============================================================ */ + +/* 发送握手消息:4 字节头 + body,并计入 transcript */ +static paze_status_t tls12_send_hs(paze_tls_t *tls, uint8_t type, + const uint8_t *body, size_t blen) { + uint8_t buf[PAZE_TLS_MAX_RECORD]; + if (blen + 4 > sizeof(buf)) return PAZE_ERR_OVERFLOW; + paze_tls_hs_put_header(buf, type, blen); + memcpy(buf + 4, body, blen); + size_t total = 4 + blen; + paze_tls_tr_update(&tls->transcript, buf, total); + return paze_tls_record_send(tls, PAZE_TLS_CT_HANDSHAKE, buf, total); +} + +/* 读取握手消息(或 ChangeCipherSpec)。收到握手消息自动计入 transcript。 + * ccs=1 时 body/blen 指向 CCS 记录(不入 transcript)。 */ +static paze_status_t tls12_recv_hs(paze_tls_t *tls, + const uint8_t **body, size_t *blen, + uint8_t *type, int *ccs, + const uint8_t **raw, size_t *raw_len) { + const uint8_t *buf; size_t n; uint8_t ct; + for (;;) { + paze_status_t st = paze_tls_record_read(tls, &buf, &n, &ct); + if (st != PAZE_OK) return st; + if (ct == PAZE_TLS_CT_ALERT) { + if (n >= 2 && buf[0] == PAZE_TLS_ALERT_FATAL) { + paze_tls_set_err(tls, "recv fatal alert %d", buf[1]); + return PAZE_ERR_PROTOCOL; + } + if (n >= 2 && buf[1] == PAZE_TLS_ALERT_CLOSE_NOTIFY) { + tls->close_recv = 1; + return PAZE_ERR_IO; + } + continue; + } + if (ct == PAZE_TLS_CT_CHANGE_CIPHER_SPEC) { + *ccs = 1; *type = PAZE_TLS_CT_CHANGE_CIPHER_SPEC; + *body = buf; *blen = n; + if (raw) { *raw = buf; *raw_len = n; } + return PAZE_OK; + } + if (ct != PAZE_TLS_CT_HANDSHAKE) { + paze_tls_set_err(tls, "unexpected ct %d during 1.2 hs", ct); + return PAZE_ERR_PROTOCOL; + } + if (n < 4) { paze_tls_set_err(tls, "short 1.2 handshake"); return PAZE_ERR_FORMAT; } + *type = buf[0]; + size_t pl = ((size_t)buf[1] << 16) | ((size_t)buf[2] << 8) | buf[3]; + if (4 + pl > n) { paze_tls_set_err(tls, "1.2 hs len mismatch"); return PAZE_ERR_FORMAT; } + *body = buf + 4; *blen = pl; + if (raw) { *raw = buf; *raw_len = 4 + pl; } + /* RFC 5246 §7.4.9: Finished 的 verify_data 覆盖"到此消息为止(不含)"; + * 故收到 Finished 时不自动计入 transcript,由调用方先验证再决定是否计入 + * (服务端需把客户端的 Finished 计入自身 Finished 的 transcript)。 */ + if (buf[0] != PAZE_TLS_HS_FINISHED) + paze_tls_tr_update(&tls->transcript, buf, 4 + pl); + *ccs = 0; + return PAZE_OK; + } +} + +/* 等对端 ChangeCipherSpec(明文)。set_read_key=1 时切换接收密钥。 */ +static paze_status_t tls12_wait_ccs(paze_tls_t *tls, int set_read_key) { + const uint8_t *body; size_t blen; uint8_t type; int ccs; + for (;;) { + paze_status_t st = tls12_recv_hs(tls, &body, &blen, &type, &ccs, NULL, NULL); + if (st != PAZE_OK) return st; + if (ccs) { + if (blen != 1 || body[0] != 0x01) { + paze_tls_set_err(tls, "bad ChangeCipherSpec"); + return PAZE_ERR_PROTOCOL; + } + if (set_read_key) { + tls->enc_read = 1; + tls->read_key = &tls->handshake_read; + } + return PAZE_OK; + } + if (type == PAZE_TLS_HS_NEW_SESSION_TICKET) continue; /* 1.2 会话票,忽略 */ + paze_tls_set_err(tls, "expected ChangeCipherSpec, got hs %d", type); + return PAZE_ERR_PROTOCOL; + } +} + +/* TLS 1.2 Finished: PRF(master_secret, label, hash(transcript), 12) */ +static paze_status_t tls12_finished(paze_tls_t *tls, const char *label, + uint8_t vd[12]) { + uint8_t th[64]; + paze_tls_tr_current(&tls->transcript, th); + paze_status_t st = paze_tls12_prf(paze_hash_sha256(), tls->ms12, 48, label, + th, 32, vd, 12); + return st; +} + +/* 由 key_block 配置单向 traffic key(fixed_iv 取 4 字节) */ +static void tls12_set_traffic_key(paze_tls_traffic_key_t *tk, + paze_tls_aead_kind_t aead, + const uint8_t *key, size_t klen, + const uint8_t *fixed_iv) { + paze_memzero(tk, sizeof(*tk)); + tk->kind = aead; + tk->key_len = klen; + memcpy(tk->key, key, klen); + memcpy(tk->iv, fixed_iv, 4); /* record 层约定 implicit IV 在前 4 字节 */ + tk->seq = 0; + if (aead == PAZE_AEAD_AES_128_GCM || aead == PAZE_AEAD_AES_256_GCM) + paze_aes_init(&tk->aes, tk->key, (int)klen * 8); +} + +/* master_secret + key_block 推导;配置 handshake_read/write */ +static paze_status_t tls12_derive_master(paze_tls_t *tls, + const uint8_t *pms, size_t pms_len) { + uint8_t seed[64]; + memcpy(seed, tls->client_random, 32); + memcpy(seed + 32, tls->server_random, 32); + paze_status_t st = paze_tls12_prf(paze_hash_sha256(), pms, pms_len, + "master secret", seed, 64, tls->ms12, 48); + if (st != PAZE_OK) return st; + tls->ms12_valid = 1; + + /* key_block: [client_MAC][server_MAC][client_key][server_key][client_IV][server_IV] + * AEAD: MAC 0, key klen, IV 4 */ + size_t klen = (tls->neg.aead == PAZE_AEAD_AES_128_GCM) ? 16 : 32; + size_t kb_len = 2 * klen + 8; + uint8_t kb[80]; + uint8_t seed2[64]; + memcpy(seed2, tls->server_random, 32); + memcpy(seed2 + 32, tls->client_random, 32); + st = paze_tls12_prf(paze_hash_sha256(), tls->ms12, 48, + "key expansion", seed2, 64, kb, kb_len); + if (st != PAZE_OK) { paze_memzero(kb, sizeof(kb)); return st; } + + paze_tls_aead_kind_t aead = tls->neg.aead; + if (tls->role == PAZE_TLS_CLIENT) { + tls12_set_traffic_key(&tls->handshake_write, aead, kb, klen, kb + 2 * klen); + tls12_set_traffic_key(&tls->handshake_read, aead, kb + klen, klen, kb + 2 * klen + 4); + } else { + tls12_set_traffic_key(&tls->handshake_read, aead, kb, klen, kb + 2 * klen); + tls12_set_traffic_key(&tls->handshake_write, aead, kb + klen, klen, kb + 2 * klen + 4); + } + paze_memzero(kb, sizeof(kb)); + paze_memzero(seed, sizeof(seed)); + paze_memzero(seed2, sizeof(seed2)); + return PAZE_OK; +} + +/* 判断 cipher 是否为 TLS 1.3 套件 */ +static int tls12_is_v13_cipher(uint16_t c) { + return c == PAZE_TLS_AES_128_GCM_SHA256 || + c == PAZE_TLS_AES_256_GCM_SHA384 || + c == PAZE_TLS_CHACHA20_POLY1305_SHA256; +} + +/* 判断 cipher 是否 ECDHE(需 ServerKeyExchange) */ +static int tls12_is_ecdhe(uint16_t c) { + return c == PAZE_TLS_ECDHE_RSA_AES128_GCM_SHA256 || + c == PAZE_TLS_ECDHE_ECDSA_AES128_GCM_SHA256 || + c == PAZE_TLS_ECDHE_RSA_CHACHA20_POLY1305; +} + +/* ============================================================ + * [客户端] 解析 ServerHello(存于 tls->sh1,含 4 字节头) + * ============================================================ */ +static paze_status_t tls12_client_parse_sh(paze_tls_t *tls) { + const uint8_t *body = tls->sh1 + 4; + size_t blen = tls->sh1_len - 4; + if (blen < 2 + 32 + 1 + 2 + 1) { paze_tls_set_err(tls, "short 1.2 SH"); return PAZE_ERR_FORMAT; } + uint16_t leg = paze_load16_be(body); + if (leg != PAZE_TLS_VERSION_1_2) { + paze_tls_set_err(tls, "1.2 SH legacy_version %04x", leg); + return PAZE_ERR_PROTOCOL; + } + memcpy(tls->server_random, body + 2, 32); + const uint8_t *p = body + 2 + 32; + uint8_t sid_len = *p++; + if ((size_t)(p - body) + sid_len + 2 + 1 > blen) { paze_tls_set_err(tls, "1.2 SH sid"); return PAZE_ERR_FORMAT; } + p += sid_len; + uint16_t cipher = paze_load16_be(p); p += 2; + p++; /* compression_method */ + if (tls12_is_v13_cipher(cipher)) { + paze_tls_set_err(tls, "server selected 1.3 cipher in 1.2 SH"); + return PAZE_ERR_PROTOCOL; + } + tls->neg.cipher = cipher; + paze_tls_cipher_params(cipher, &tls->neg.aead, &tls->neg.hash, + &tls->neg.key_len, &tls->neg.hash_len); + if (tls->neg.aead == PAZE_AEAD_NONE) { + paze_tls_set_err(tls, "1.2 cipher %04x not supported", cipher); + return PAZE_ERR_NOTSUP; + } + tls->neg.version = PAZE_TLS_VERSION_1_2; + return PAZE_OK; +} + +/* ============================================================ + * [客户端] 解析 Certificate(TLS 1.2 格式) + * ============================================================ */ +static paze_status_t tls12_client_parse_cert(paze_tls_t *tls, + const uint8_t *body, size_t blen) { + if (blen < 3) return PAZE_ERR_FORMAT; + size_t total = ((size_t)body[0] << 16) | ((size_t)body[1] << 8) | body[2]; + if (total != blen - 3) return PAZE_ERR_FORMAT; + size_t p = 3, i = 0; + tls->peer_chain_len = 0; + while (p < blen && i < PAZE_TLS_MAX_CERTS) { + if (blen < p + 3) return PAZE_ERR_FORMAT; + size_t cl = ((size_t)body[p] << 16) | ((size_t)body[p+1] << 8) | body[p+2]; + p += 3; + if (blen < p + cl) return PAZE_ERR_FORMAT; + paze_status_t st = paze_x509_parse_der(&tls->peer_chain[i], body + p, cl); + if (st != PAZE_OK) return PAZE_ERR_FORMAT; + tls->peer_chain_len++; i++; p += cl; + } + return PAZE_OK; +} + +/* ============================================================ + * [客户端] 解析 ServerKeyExchange(ECDHE):params + 签名验证 + * ============================================================ */ +static paze_status_t tls12_client_parse_ske(paze_tls_t *tls, + const uint8_t *body, size_t blen) { + if (blen < 4) return PAZE_ERR_FORMAT; + uint8_t curve_type = body[0]; + if (curve_type != 0x03) { paze_tls_set_err(tls, "unsupported curve_type"); return PAZE_ERR_NOTSUP; } + uint16_t nc = paze_load16_be(body + 1); + uint8_t plen = body[3]; + if (blen < (size_t)(4 + plen + 4)) return PAZE_ERR_FORMAT; + size_t params_len = 4 + plen; + const uint8_t *params = body; + uint16_t sig_alg = paze_load16_be(body + params_len); + uint16_t sig_len = paze_load16_be(body + params_len + 2); + if (blen != params_len + 4 + sig_len) return PAZE_ERR_FORMAT; + const uint8_t *sig = body + params_len + 4; + + tls->neg.group = nc; + if (plen > sizeof(tls->ske_pub)) return PAZE_ERR_FORMAT; + memcpy(tls->ske_pub, body + 4, plen); + tls->ske_pub_len = plen; + + /* 签名数据 = client_random || server_random || params */ + uint8_t sd[32 + 32 + 70]; + memcpy(sd, tls->client_random, 32); + memcpy(sd + 32, tls->server_random, 32); + memcpy(sd + 64, params, params_len); + size_t sd_len = 64 + params_len; + + uint8_t h[64]; size_t hl; paze_rsa_hash_t rh; + switch (sig_alg) { + case PAZE_TLS_SIG_RSA_PKCS1_SHA256: + case PAZE_TLS_SIG_RSA_PSS_RSAE_SHA256: + rh = PAZE_RSA_HASH_SHA256; hl = 32; break; + case PAZE_TLS_SIG_RSA_PKCS1_SHA384: + case PAZE_TLS_SIG_RSA_PSS_RSAE_SHA384: + rh = PAZE_RSA_HASH_SHA384; hl = 48; break; + case PAZE_TLS_SIG_ECDSA_SECP256R1_SHA256: + rh = PAZE_RSA_HASH_SHA256; hl = 32; break; + default: + paze_tls_set_err(tls, "1.2 SKE sig alg %04x", sig_alg); + return PAZE_ERR_NOTSUP; + } + if (rh == PAZE_RSA_HASH_SHA256) paze_sha256(sd, sd_len, h); + else paze_sha384(sd, sd_len, h); + + const paze_x509_cert_t *leaf = &tls->peer_chain[0]; + paze_status_t st; + if (leaf->pk_kind == PAZE_X509_PK_RSA) { + if (sig_alg == PAZE_TLS_SIG_RSA_PSS_RSAE_SHA256 || sig_alg == PAZE_TLS_SIG_RSA_PSS_RSAE_SHA384) + st = paze_rsa_pss_verify(&leaf->rsa_pub, rh, h, hl, sig, sig_len); + else + st = paze_rsa_pkcs1_verify(&leaf->rsa_pub, rh, h, hl, sig, sig_len); + } else if (leaf->pk_kind == PAZE_X509_PK_ECDSA_P256 && sig_alg == PAZE_TLS_SIG_ECDSA_SECP256R1_SHA256) { + st = paze_ecdsa_verify(&leaf->ecdsa_pub, rh, h, hl, sig, sig_len); + } else { + st = PAZE_ERR_VERIFY; + } + if (st != PAZE_OK) { + paze_tls_set_err(tls, "1.2 SKE signature verify failed"); + return PAZE_ERR_VERIFY; + } + return PAZE_OK; +} + +/* ============================================================ + * [客户端] 证书链验证(与 1.3 客户端一致) + * ============================================================ */ +static paze_status_t tls12_client_verify_cert(paze_tls_t *tls) { + paze_tls_config_t *cfg = tls->cfg_owned; + if (cfg->verify_mode == PAZE_TLS_VERIFY_NONE || tls->peer_chain_len == 0) return PAZE_OK; + paze_status_t vs = PAZE_OK; + if (cfg->anchors && cfg->anchor_count > 0) { + vs = paze_x509_verify_chain(tls->peer_chain, tls->peer_chain_len, + cfg->anchors, cfg->anchor_count, + (int64_t)time(NULL)); + } else if (cfg->verify_mode == PAZE_TLS_VERIFY_REQUIRED) { + vs = PAZE_ERR_VERIFY; + } + if (cfg->sni[0] && tls->peer_chain_len > 0) { + if (!paze_x509_match_host(&tls->peer_chain[0], cfg->sni)) { + if (vs == PAZE_OK) vs = PAZE_ERR_VERIFY; + } + } + if (cfg->verify_cb) { + if (!cfg->verify_cb(cfg->verify_ctx, tls->peer_chain, tls->peer_chain_len, vs)) + return PAZE_ERR_VERIFY; + } else if (cfg->verify_mode == PAZE_TLS_VERIFY_REQUIRED && vs != PAZE_OK) { + paze_tls_set_err(tls, "cert verification failed (%d)", vs); + return PAZE_ERR_VERIFY; + } + return PAZE_OK; +} + +/* ============================================================ + * [客户端] 构造 ClientKeyExchange 并缓存 pre_master_secret + * ============================================================ */ +static paze_status_t tls12_client_build_cke(paze_tls_t *tls, + uint8_t *out, size_t *outlen) { + const paze_x509_cert_t *leaf = &tls->peer_chain[0]; + if (tls12_is_ecdhe(tls->neg.cipher)) { + const uint8_t *pub; size_t plen; + if (tls->neg.group == PAZE_TLS_GROUP_X25519) { pub = tls->kex.x25519_pub; plen = 32; } + else if (tls->neg.group == PAZE_TLS_GROUP_SECP256R1) { pub = tls->kex.p256_pub; plen = 65; } + else { paze_tls_set_err(tls, "1.2 group %04x not supported", tls->neg.group); return PAZE_ERR_NOTSUP; } + if (1 + plen > 128) return PAZE_ERR_OVERFLOW; + out[0] = (uint8_t)plen; /* RFC 5246 §7.4.7:ECDHE point 用 1 字节长度 */ + memcpy(out + 1, pub, plen); + *outlen = 1 + plen; + + uint8_t shared[64]; size_t shared_len = 0; + paze_status_t st = paze_tls_kex_ecdh(&tls->kex, tls->neg.group, + tls->ske_pub, tls->ske_pub_len, + shared, &shared_len); + if (st != PAZE_OK) return st; + memcpy(tls->pms12, shared, shared_len); + tls->pms12_len = shared_len; + paze_memzero(shared, sizeof(shared)); + return PAZE_OK; + } + /* 静态 RSA */ + if (leaf->pk_kind != PAZE_X509_PK_RSA) { + paze_tls_set_err(tls, "RSA cipher but no RSA cert"); + return PAZE_ERR_VERIFY; + } + uint8_t pms[48]; + pms[0] = 0x03; pms[1] = 0x03; + if (paze_random(pms + 2, 44) != PAZE_OK) return PAZE_ERR_PLATFORM; + size_t k = paze_rsa_modulus_len(&leaf->rsa_pub.n); + uint8_t enc[512]; + paze_status_t st = paze_rsa_pkcs1_encrypt(&leaf->rsa_pub, pms, 46, enc, k); + if (st != PAZE_OK) return st; + paze_store16_be(out, (uint16_t)k); + memcpy(out + 2, enc, k); + *outlen = 2 + k; + memcpy(tls->pms12, pms, 46); + tls->pms12_len = 46; + paze_memzero(pms, sizeof(pms)); + paze_memzero(enc, sizeof(enc)); + return PAZE_OK; +} + +/* ============================================================ + * [客户端] 主入口 + * ============================================================ */ paze_status_t paze_tls12_client_handshake(paze_tls_t *tls) { - (void)tls; - paze_set_err("TLS 1.2 handshake not implemented yet"); - return PAZE_ERR_NOTSUP; + paze_status_t st; + + /* ---- 阶段 A:发送 ClientHello / 等待并解析 ServerHello ---- */ + if (tls->hs_state == PAZE_HS_INIT) { + st = paze_tls_kex_generate(&tls->kex, 0); + if (st != PAZE_OK) return st; + paze_tls_tr_init(&tls->transcript, paze_hash_sha256()); + 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; + 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); + paze_tls_record_send_plain(tls, PAZE_TLS_CT_HANDSHAKE, ch, ch_len); + tls->hs_state = PAZE_HS_CL12_WAIT_SHD; + } + if (tls->hs_state == PAZE_HS_CL12_WAIT_SHD || tls->hs_state == PAZE_HS_CL_WAIT_SH) { + /* 从 1.3 回退时 sh1 已保存;独立模式需读取 */ + if (!tls->sh1) { + const uint8_t *body, *raw; size_t blen, raw_len; uint8_t type; int ccs; + st = tls12_recv_hs(tls, &body, &blen, &type, &ccs, &raw, &raw_len); + if (st != PAZE_OK) return st; + if (ccs || type != PAZE_TLS_HS_SERVER_HELLO) { + paze_tls_set_err(tls, "expected 1.2 ServerHello, got %d", type); + return PAZE_ERR_PROTOCOL; + } + tls->sh1 = (uint8_t *)malloc(raw_len); + if (!tls->sh1) return PAZE_ERR_PLATFORM; + memcpy(tls->sh1, raw, raw_len); + tls->sh1_len = raw_len; + } + /* 记录 client_random(CH body 偏移:头 4 + legacy_ver 2) */ + if (tls->ch1 && tls->ch1_len >= 4 + 34) { + memcpy(tls->client_random, tls->ch1 + 4 + 2, 32); + } + st = tls12_client_parse_sh(tls); + if (st != PAZE_OK) return st; + tls->hs_state = PAZE_HS_CL12_WAIT_CERT_SKE_SHD; + } + + /* ---- 阶段 B:Certificate / ServerKeyExchange / ServerHelloDone ---- */ + while (tls->hs_state == PAZE_HS_CL12_WAIT_CERT_SKE_SHD) { + const uint8_t *body; size_t blen; uint8_t type; int ccs; + st = tls12_recv_hs(tls, &body, &blen, &type, &ccs, NULL, NULL); + if (st != PAZE_OK) return st; + if (ccs) { paze_tls_set_err(tls, "unexpected CCS before SHD"); return PAZE_ERR_PROTOCOL; } + if (type == PAZE_TLS_HS_CERTIFICATE_REQUEST) continue; /* 客户端证书请求:忽略 */ + if (type == PAZE_TLS_HS_CERTIFICATE) { + st = tls12_client_parse_cert(tls, body, blen); + if (st != PAZE_OK) return st; + continue; + } + if (type == PAZE_TLS_HS_SERVER_KEY_EXCHANGE) { + st = tls12_client_parse_ske(tls, body, blen); + if (st != PAZE_OK) return st; + continue; + } + if (type == PAZE_TLS_HS_SERVER_HELLO_DONE) break; + paze_tls_set_err(tls, "unexpected hs %d in 1.2", type); + return PAZE_ERR_PROTOCOL; + } + if (tls->hs_state == PAZE_HS_CL12_WAIT_CERT_SKE_SHD) { + if (tls->peer_chain_len == 0) { + paze_tls_set_err(tls, "no server certificate in 1.2"); + return PAZE_ERR_PROTOCOL; + } + st = tls12_client_verify_cert(tls); + if (st != PAZE_OK) return st; + /* 构造 CKE(缓存 pre-master secret) */ + uint8_t cke[600]; size_t cke_len = 0; + st = tls12_client_build_cke(tls, cke, &cke_len); + if (st != PAZE_OK) return st; + st = tls12_send_hs(tls, PAZE_TLS_HS_CLIENT_KEY_EXCHANGE, cke, cke_len); + if (st != PAZE_OK) return st; + paze_memzero(cke, sizeof(cke)); + tls->hs_state = PAZE_HS_CL12_SEND_CKE; + } + + /* ---- 阶段 C:发送 CCS + Finished ---- */ + if (tls->hs_state == PAZE_HS_CL12_SEND_CKE) { + st = tls12_derive_master(tls, tls->pms12, tls->pms12_len); + paze_memzero(tls->pms12, sizeof(tls->pms12)); + tls->pms12_len = 0; + if (st != PAZE_OK) return st; + + uint8_t ccs[1] = { 0x01 }; + st = paze_tls_record_send_plain(tls, PAZE_TLS_CT_CHANGE_CIPHER_SPEC, ccs, 1); + if (st != PAZE_OK) return st; + tls->enc_write = 1; + tls->write_key = &tls->handshake_write; + + uint8_t vd[12]; + st = tls12_finished(tls, "client finished", vd); + if (st != PAZE_OK) return st; + st = tls12_send_hs(tls, PAZE_TLS_HS_FINISHED, vd, 12); + if (st != PAZE_OK) return st; + paze_memzero(vd, sizeof(vd)); + tls->hs_state = PAZE_HS_CL12_WAIT_CCS_FIN; + } + + /* ---- 阶段 D:等 CCS + Finished,验证 ---- */ + while (tls->hs_state == PAZE_HS_CL12_WAIT_CCS_FIN) { + st = tls12_wait_ccs(tls, 1); + if (st != PAZE_OK) return st; + const uint8_t *body; size_t blen; uint8_t type; int ccs; + st = tls12_recv_hs(tls, &body, &blen, &type, &ccs, NULL, NULL); + if (st != PAZE_OK) return st; + if (ccs) { paze_tls_set_err(tls, "duplicate CCS"); return PAZE_ERR_PROTOCOL; } + if (type == PAZE_TLS_HS_NEW_SESSION_TICKET) continue; + if (type != PAZE_TLS_HS_FINISHED) { + paze_tls_set_err(tls, "expected server Finished, got %d", type); + return PAZE_ERR_PROTOCOL; + } + if (blen != 12) { paze_tls_set_err(tls, "bad 1.2 Finished len"); return PAZE_ERR_VERIFY; } + uint8_t exp[12]; + st = tls12_finished(tls, "server finished", exp); + if (st != PAZE_OK) return st; + if (paze_ct_equal(exp, body, 12) != 0) { + paze_tls_set_err(tls, "server 1.2 Finished verify failed"); + return PAZE_ERR_VERIFY; + } + paze_memzero(exp, sizeof(exp)); + tls->handshake_done = 1; + tls->hs_state = PAZE_HS_CL_DONE; + } + return PAZE_OK; } +/* ============================================================ + * [服务端] ClientHello 解析(存于 tls->ch1,含 4 字节头) + * ============================================================ */ +typedef struct { + uint16_t group; /* supported_groups 首选 */ + char sni[PAZE_TLS_SNI_MAX + 1]; + int have_sni; + int have_renego; +} ch12_ext_ctx_t; + +static int ch12_ext_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { + ch12_ext_ctx_t *s = (ch12_ext_ctx_t *)x; + if (tp == PAZE_TLS_EXT_SUPPORTED_GROUPS) { + s->group = paze_tls_ext_parse_supported_groups(b, l); + } else if (tp == PAZE_TLS_EXT_SERVER_NAME) { + if (paze_tls_ext_parse_sni(b, l, s->sni)) s->have_sni = 1; + } else if (tp == PAZE_TLS_EXT_RENEGOTIATION_INFO) { + s->have_renego = 1; + } + return 0; +} + +static paze_status_t tls12_server_parse_ch(paze_tls_t *tls, + uint16_t *client_suites, size_t *n_suites) { + const uint8_t *body = tls->ch1 + 4; + size_t blen = tls->ch1_len - 4; + if (blen < 2 + 32 + 1 + 2) return PAZE_ERR_FORMAT; + memcpy(tls->client_random, body + 2, 32); + const uint8_t *p = body + 2 + 32; + uint8_t sid_len = *p++; + if ((size_t)(p - body) + sid_len + 2 > blen) return PAZE_ERR_FORMAT; + p += sid_len; /* sid 原样回显(阶段 B 直接从 ch1 读取) */ + size_t slen = paze_load16_be(p); p += 2; + if ((size_t)(p - body) + slen > blen) return PAZE_ERR_FORMAT; + if (slen & 1) return PAZE_ERR_FORMAT; + *n_suites = slen / 2; + if (*n_suites > 64) return PAZE_ERR_FORMAT; + for (size_t i = 0; i < *n_suites; i++) + client_suites[i] = paze_load16_be(p + 2 * i); + p += slen; + if ((size_t)(p - body) + 1 > blen) return PAZE_ERR_FORMAT; + uint8_t comp_len = *p++; + if ((size_t)(p - body) + comp_len > blen) return PAZE_ERR_FORMAT; + p += comp_len; + return PAZE_OK; +} + +/* 选择 1.2 cipher:按服务端偏好,过滤私钥类型 */ +static uint16_t tls12_pick_cipher(const uint16_t *list, size_t n, int priv_is_ecdsa) { + static const uint16_t pref_rsa[] = { + PAZE_TLS_ECDHE_RSA_AES128_GCM_SHA256, + PAZE_TLS_ECDHE_RSA_CHACHA20_POLY1305, + PAZE_TLS_RSA_AES128_GCM_SHA256 + }; + static const uint16_t pref_ecdsa[] = { + PAZE_TLS_ECDHE_ECDSA_AES128_GCM_SHA256 + }; + const uint16_t *pref = priv_is_ecdsa ? pref_ecdsa : pref_rsa; + size_t pn = priv_is_ecdsa ? 1 : 3; + for (size_t i = 0; i < pn; i++) { + for (size_t j = 0; j < n; j++) { + if (list[j] == pref[i]) return pref[i]; + } + } + return 0; +} + +/* ============================================================ + * [服务端] 构造 ServerKeyExchange 并签名 + * ============================================================ */ +static paze_status_t tls12_server_build_ske(paze_tls_t *tls, + uint8_t *out, size_t *outlen) { + paze_tls_config_t *cfg = tls->cfg_owned; + uint16_t nc = tls->neg.group; + const uint8_t *pub; size_t plen; + if (nc == PAZE_TLS_GROUP_X25519) { pub = tls->kex.x25519_pub; plen = 32; } + else if (nc == PAZE_TLS_GROUP_SECP256R1) { pub = tls->kex.p256_pub; plen = 65; } + else return PAZE_ERR_NOTSUP; + + out[0] = 0x03; + paze_store16_be(out + 1, nc); + out[3] = (uint8_t)plen; + memcpy(out + 4, pub, plen); + size_t params_len = 4 + plen; + + /* 签名数据 = client_random || server_random || params */ + uint8_t sd[32 + 32 + 70]; + memcpy(sd, tls->client_random, 32); + memcpy(sd + 32, tls->server_random, 32); + memcpy(sd + 64, out, params_len); + size_t sd_len = 64 + params_len; + + size_t sig_cap = 512; /* RSA-2048 签名 256 字节;ECDSA P-256 签名 ≤ 70 */ + uint8_t *sig = out + params_len + 4; + size_t sig_len = sig_cap; + uint16_t sig_alg; + uint8_t h[64]; + if (cfg->privkey.kind == PAZE_X509_PK_RSA) { + sig_alg = PAZE_TLS_SIG_RSA_PKCS1_SHA256; + paze_sha256(sd, sd_len, h); + paze_status_t st = paze_rsa_pkcs1_sign(&cfg->privkey.rsa, PAZE_RSA_HASH_SHA256, + h, 32, sig, sig_len); + if (st != PAZE_OK) return st; + sig_len = paze_rsa_modulus_len(&cfg->privkey.rsa.n); + } else if (cfg->privkey.kind == PAZE_X509_PK_ECDSA_P256) { + sig_alg = PAZE_TLS_SIG_ECDSA_SECP256R1_SHA256; + paze_sha256(sd, sd_len, h); + paze_ecdsa_priv_t ek; + paze_ecdsa_priv_set_d(&ek, cfg->privkey.ecdsa_d); + paze_status_t st = paze_ecdsa_sign(&ek, PAZE_RSA_HASH_SHA256, h, 32, sig, &sig_len); + if (st != PAZE_OK) return st; + } else { + return PAZE_ERR_NOTSUP; + } + paze_store16_be(out + params_len, sig_alg); + paze_store16_be(out + params_len + 2, (uint16_t)sig_len); + *outlen = params_len + 4 + sig_len; + paze_memzero(sd, sizeof(sd)); + return PAZE_OK; +} + +/* ============================================================ + * [服务端] 解析 ClientKeyExchange 得 pre-master secret + * ============================================================ */ +static paze_status_t tls12_server_parse_cke(paze_tls_t *tls, + const uint8_t *body, size_t blen) { + paze_tls_config_t *cfg = tls->cfg_owned; + if (tls12_is_ecdhe(tls->neg.cipher)) { + if (blen < 1) return PAZE_ERR_FORMAT; + size_t plen = body[0]; /* RFC 5246 §7.4.7:ECDHE point 用 1 字节长度 */ + if (blen != 1 + plen) return PAZE_ERR_FORMAT; + uint8_t shared[64]; size_t shared_len = 0; + paze_status_t st = paze_tls_kex_ecdh(&tls->kex, tls->neg.group, + body + 1, plen, shared, &shared_len); + if (st != PAZE_OK) return st; + memcpy(tls->pms12, shared, shared_len); + tls->pms12_len = shared_len; + paze_memzero(shared, sizeof(shared)); + return PAZE_OK; + } + /* 静态 RSA:解密 pre-master secret */ + if (cfg->privkey.kind != PAZE_X509_PK_RSA) return PAZE_ERR_VERIFY; + if (blen < 2) return PAZE_ERR_FORMAT; + size_t k = paze_load16_be(body); + if (blen != 2 + k) return PAZE_ERR_FORMAT; + uint8_t pms[48]; size_t pms_len = sizeof(pms); + paze_status_t st = paze_rsa_pkcs1_decrypt(&cfg->privkey.rsa, body + 2, k, + pms, &pms_len); + if (st != PAZE_OK) return st; + if (pms_len != 46 || pms[0] != 0x03 || pms[1] != 0x03) return PAZE_ERR_VERIFY; + memcpy(tls->pms12, pms, 46); + tls->pms12_len = 46; + paze_memzero(pms, sizeof(pms)); + return PAZE_OK; +} + +/* ============================================================ + * [服务端] 主入口 + * ============================================================ */ paze_status_t paze_tls12_server_handshake(paze_tls_t *tls) { - (void)tls; - paze_set_err("TLS 1.2 server handshake not implemented yet"); - return PAZE_ERR_NOTSUP; + paze_tls_config_t *cfg = tls->cfg_owned; + paze_status_t st; + uint16_t client_suites[64]; size_t n_suites = 0; + + /* ---- 阶段 A:解析 ClientHello、选择参数 ---- */ + if (tls->hs_state == PAZE_HS_INIT) { + int ch_was_present = (tls->ch1 != NULL); + /* 必须先初始化 transcript,再收 ClientHello(recv 会将其计入) */ + paze_tls_tr_init(&tls->transcript, paze_hash_sha256()); + if (!tls->ch1) { + const uint8_t *body, *raw; size_t blen, raw_len; uint8_t type; int ccs; + st = tls12_recv_hs(tls, &body, &blen, &type, &ccs, &raw, &raw_len); + if (st != PAZE_OK) return st; + if (ccs || type != PAZE_TLS_HS_CLIENT_HELLO) { + paze_tls_set_err(tls, "expected ClientHello, got %d", type); + return PAZE_ERR_PROTOCOL; + } + tls->ch1 = (uint8_t *)malloc(raw_len); + if (!tls->ch1) return PAZE_ERR_PLATFORM; + memcpy(tls->ch1, raw, raw_len); + tls->ch1_len = raw_len; + } + /* 仅从 1.3 回退(ch1 已存在)时需要重放:recv_hs 已把 CH 计入旧 + * transcript,重建后必须重放;独立模式 recv_hs 已计入新 transcript。 */ + if (ch_was_present) + paze_tls_tr_update(&tls->transcript, tls->ch1, tls->ch1_len); + + st = tls12_server_parse_ch(tls, client_suites, &n_suites); + if (st != PAZE_OK) return st; + + /* 选 cipher */ + int priv_is_ecdsa = (cfg->privkey.kind == PAZE_X509_PK_ECDSA_P256); + uint16_t cipher = tls12_pick_cipher(client_suites, n_suites, priv_is_ecdsa); + if (!cipher) { + paze_tls_set_err(tls, "no common 1.2 cipher suite"); + return PAZE_ERR_NOTSUP; + } + tls->neg.cipher = cipher; + paze_tls_cipher_params(cipher, &tls->neg.aead, &tls->neg.hash, + &tls->neg.key_len, &tls->neg.hash_len); + tls->neg.version = PAZE_TLS_VERSION_1_2; + + /* 选 group(仅 ECDHE):客户端 supported_groups 优先,缺省 P-256 */ + ch12_ext_ctx_t ex; memset(&ex, 0, sizeof(ex)); + if (tls->ch1_len >= 4 + 2) { + const uint8_t *body = tls->ch1 + 4; + size_t blen = tls->ch1_len - 4; + const uint8_t *p = body + 2 + 32; + uint8_t sl = *p++; + p += sl; + size_t csl = paze_load16_be(p); p += 2; + p += csl; + uint8_t cl = *p++; + p += cl; + if ((size_t)(p - body) + 2 <= blen) { + size_t ext_total = paze_load16_be(p); p += 2; + if ((size_t)(p - body) + ext_total <= blen) + paze_tls_extensions_foreach(p - 2, 2 + ext_total, ch12_ext_cb, &ex); + } + } + tls->neg.group = ex.group ? ex.group : PAZE_TLS_GROUP_SECP256R1; + if (tls12_is_ecdhe(cipher)) { + st = paze_tls_kex_generate(&tls->kex, tls->neg.group == PAZE_TLS_GROUP_SECP256R1); + if (st != PAZE_OK) return st; + } + + if (paze_random(tls->server_random, 32) != PAZE_OK) return PAZE_ERR_PLATFORM; + tls->hs_state = PAZE_HS_SV12_SEND_SH_CERT_SKE_SHD; + } + + /* ---- 阶段 B:ServerHello + Certificate [+ SKE] + ServerHelloDone ---- */ + while (tls->hs_state == PAZE_HS_SV12_SEND_SH_CERT_SKE_SHD) { + /* ServerHello */ + uint8_t sh[1024]; + uint8_t *p = sh + 4; + paze_store16_be(p, PAZE_TLS_LEGACY_VERSION); p += 2; + memcpy(p, tls->server_random, 32); p += 32; + const uint8_t *chp = tls->ch1 + 4 + 2 + 32; + uint8_t ch_sid_len = *chp++; + *p++ = ch_sid_len; + memcpy(p, chp, ch_sid_len); p += ch_sid_len; + paze_store16_be(p, tls->neg.cipher); p += 2; + *p++ = 0x00; /* compression_method = null */ + uint8_t *extp = p; p += 2; + /* renegotiation_info(空)回显 */ + size_t n = paze_tls_ext_write_renegotiation_info(p); p += n; + paze_store16_be(extp, (uint16_t)(p - extp - 2)); + size_t body_len = (size_t)(p - (sh + 4)); + paze_tls_hs_put_header(sh, PAZE_TLS_HS_SERVER_HELLO, body_len); + st = paze_tls_record_send_plain(tls, PAZE_TLS_CT_HANDSHAKE, sh, 4 + body_len); + if (st != PAZE_OK) return st; + paze_tls_tr_update(&tls->transcript, sh, 4 + body_len); + + /* Certificate(TLS 1.2 格式) */ + if (!cfg->cert_der || cfg->cert_der_len == 0) { + paze_tls_set_err(tls, "no server certificate configured"); + return PAZE_ERR_INVALID; + } + if (cfg->cert_der_len > 0xFFFFFF) return PAZE_ERR_OVERFLOW; + uint8_t cert_body[8192]; + cert_body[0] = (uint8_t)(cfg->cert_der_len >> 16); + cert_body[1] = (uint8_t)(cfg->cert_der_len >> 8); + cert_body[2] = (uint8_t)(cfg->cert_der_len); + if (3 + cfg->cert_der_len > sizeof(cert_body)) return PAZE_ERR_OVERFLOW; + memcpy(cert_body + 3, cfg->cert_der, cfg->cert_der_len); + st = tls12_send_hs(tls, PAZE_TLS_HS_CERTIFICATE, cert_body, 3 + cfg->cert_der_len); + if (st != PAZE_OK) return st; + + /* ServerKeyExchange(ECDHE 时) */ + if (tls12_is_ecdhe(tls->neg.cipher)) { + uint8_t ske[1024]; size_t ske_len = sizeof(ske); + st = tls12_server_build_ske(tls, ske, &ske_len); + if (st != PAZE_OK) return st; + st = tls12_send_hs(tls, PAZE_TLS_HS_SERVER_KEY_EXCHANGE, ske, ske_len); + if (st != PAZE_OK) return st; + } + + /* ServerHelloDone */ + st = tls12_send_hs(tls, PAZE_TLS_HS_SERVER_HELLO_DONE, NULL, 0); + if (st != PAZE_OK) return st; + tls->hs_state = PAZE_HS_SV12_WAIT_CKE; + } + + /* ---- 阶段 C:ClientKeyExchange ---- */ + while (tls->hs_state == PAZE_HS_SV12_WAIT_CKE) { + const uint8_t *body; size_t blen; uint8_t type; int ccs; + st = tls12_recv_hs(tls, &body, &blen, &type, &ccs, NULL, NULL); + if (st != PAZE_OK) return st; + if (ccs) { paze_tls_set_err(tls, "unexpected CCS before CKE"); return PAZE_ERR_PROTOCOL; } + if (type != PAZE_TLS_HS_CLIENT_KEY_EXCHANGE) { + paze_tls_set_err(tls, "expected CKE, got hs %d", type); + return PAZE_ERR_PROTOCOL; + } + st = tls12_server_parse_cke(tls, body, blen); + if (st != PAZE_OK) return st; + st = tls12_derive_master(tls, tls->pms12, tls->pms12_len); + paze_memzero(tls->pms12, sizeof(tls->pms12)); + tls->pms12_len = 0; + if (st != PAZE_OK) return st; + tls->hs_state = PAZE_HS_SV12_WAIT_CCS_FIN; + } + + /* ---- 阶段 D:等客户端 CCS + Finished,验证 ---- */ + while (tls->hs_state == PAZE_HS_SV12_WAIT_CCS_FIN) { + st = tls12_wait_ccs(tls, 1); + if (st != PAZE_OK) return st; + const uint8_t *body, *raw; size_t blen, raw_len; uint8_t type; int ccs; + st = tls12_recv_hs(tls, &body, &blen, &type, &ccs, &raw, &raw_len); + if (st != PAZE_OK) return st; + if (ccs) { paze_tls_set_err(tls, "duplicate CCS"); return PAZE_ERR_PROTOCOL; } + if (type == PAZE_TLS_HS_NEW_SESSION_TICKET) continue; + if (type != PAZE_TLS_HS_FINISHED) { + paze_tls_set_err(tls, "expected client Finished, got %d", type); + return PAZE_ERR_PROTOCOL; + } + if (blen != 12) { paze_tls_set_err(tls, "bad 1.2 Finished len"); return PAZE_ERR_VERIFY; } + uint8_t exp[12]; + st = tls12_finished(tls, "client finished", exp); + if (st != PAZE_OK) return st; + if (paze_ct_equal(exp, body, 12) != 0) { + paze_tls_set_err(tls, "client 1.2 Finished verify failed"); + return PAZE_ERR_VERIFY; + } + paze_memzero(exp, sizeof(exp)); + /* RFC 5246 §7.4.9: 服务端自身的 Finished 需把客户端的 Finished 计入 + * transcript;而 tls12_recv_hs 收 Finished 时不自动计入,此处显式补上。 */ + paze_tls_tr_update(&tls->transcript, raw, raw_len); + tls->hs_state = PAZE_HS_SV12_SEND_CCS_FIN; + } + + /* ---- 阶段 E:发 CCS + Finished ---- */ + if (tls->hs_state == PAZE_HS_SV12_SEND_CCS_FIN) { + uint8_t ccs[1] = { 0x01 }; + st = paze_tls_record_send_plain(tls, PAZE_TLS_CT_CHANGE_CIPHER_SPEC, ccs, 1); + if (st != PAZE_OK) return st; + tls->enc_write = 1; + tls->write_key = &tls->handshake_write; + + uint8_t vd[12]; + st = tls12_finished(tls, "server finished", vd); + if (st != PAZE_OK) return st; + st = tls12_send_hs(tls, PAZE_TLS_HS_FINISHED, vd, 12); + if (st != PAZE_OK) return st; + paze_memzero(vd, sizeof(vd)); + tls->handshake_done = 1; + tls->hs_state = PAZE_HS_SV_DONE; + } + return PAZE_OK; } diff --git a/src/ssl/tls_handshake13.c b/src/ssl/tls_handshake13.c index 8d02cfa..333ac87 100644 --- a/src/ssl/tls_handshake13.c +++ b/src/ssl/tls_handshake13.c @@ -76,7 +76,8 @@ static paze_status_t send_hs(paze_tls_t *tls, uint8_t type, const uint8_t *body, } static paze_status_t recv_hs(paze_tls_t *tls, - const uint8_t **body, size_t *blen, uint8_t *type) { + const uint8_t **body, size_t *blen, uint8_t *type, + const uint8_t **raw, size_t *raw_len) { const uint8_t *buf; size_t n; uint8_t ct; for (;;) { paze_status_t st = paze_tls_record_read(tls, &buf, &n, &ct); @@ -92,6 +93,9 @@ static paze_status_t recv_hs(paze_tls_t *tls, } continue; } + if (ct == PAZE_TLS_CT_CHANGE_CIPHER_SPEC) { + continue; /* 兼容性 CCS(1.2 互操作):忽略 */ + } if (ct != PAZE_TLS_CT_HANDSHAKE) { if (!tls->handshake_done) { paze_tls_set_err(tls, "unexpected ct %d during hs", ct); @@ -105,7 +109,13 @@ static paze_status_t recv_hs(paze_tls_t *tls, size_t pl = ((size_t)buf[1] << 16) | ((size_t)buf[2] << 8) | buf[3]; if (4 + pl > n) { paze_tls_set_err(tls, "hs len mismatch"); return PAZE_ERR_FORMAT; } *body = buf + 4; *blen = pl; - paze_tls_tr_update(&tls->transcript, buf, 4 + pl); + if (raw) { *raw = buf; *raw_len = 4 + pl; } + /* RFC 8446 §4.4.3/§4.4.4:CV 的签名与 Finished 的 verify_data 都 + * 覆盖"到该消息为止(不含)"的 transcript;故不在此自动计入, + * 由调用方先验证再把原始消息显式计入。 */ + if (buf[0] != PAZE_TLS_HS_CERTIFICATE_VERIFY && + buf[0] != PAZE_TLS_HS_FINISHED) + paze_tls_tr_update(&tls->transcript, buf, 4 + pl); return PAZE_OK; } } @@ -113,7 +123,7 @@ static paze_status_t recv_hs(paze_tls_t *tls, /* ============================================================ * [B] ClientHello 构造 * ============================================================ */ -static paze_status_t 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) { paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg; uint8_t *p = out; p += 4; @@ -347,8 +357,13 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { paze_tls_tr_init(&tls->transcript, paze_hash_sha256()); uint8_t ch[PAZE_TLS_MAX_RECORD]; size_t ch_len = 0; - st = tls13_build_ch(tls, ch, &ch_len); + 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); @@ -357,7 +372,8 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { while (tls->hs_state == PAZE_HS_CL_WAIT_SH) { const uint8_t *body; size_t blen; uint8_t type; - st = recv_hs(tls, &body, &blen, &type); + const uint8_t *sh_raw = NULL; size_t sh_raw_len = 0; + st = recv_hs(tls, &body, &blen, &type, &sh_raw, &sh_raw_len); if (st != PAZE_OK) return st; if (type != PAZE_TLS_HS_SERVER_HELLO) { paze_tls_set_err(tls, "expected ServerHello, got %d", type); @@ -385,11 +401,25 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { case PAZE_TLS_CHACHA20_POLY1305_SHA256: tls->neg.version = PAZE_TLS_VERSION_1_3; break; default: - tls->neg.version = PAZE_TLS_VERSION_1_2; + /* TLS 1.2 ServerHello:保存原始消息供 tls_handshake12 使用, + * 返回 NOTSUP 由 tls_conn 回退到 1.2。 */ + if (sh_raw && sh_raw_len) { + free(tls->sh1); + tls->sh1 = (uint8_t *)malloc(sh_raw_len); + if (!tls->sh1) return PAZE_ERR_PLATFORM; + memcpy(tls->sh1, sh_raw, sh_raw_len); + tls->sh1_len = sh_raw_len; + } return PAZE_ERR_NOTSUP; } - if (tls->neg.hash == paze_hash_sha384()) return PAZE_ERR_NOTSUP; + /* 若协商 hash 为 SHA-384,transcript 需用 SHA-384 从头重算: + * recv_hs 已把 CH+SH 算入 SHA-256 transcript,这里重建为 SHA-384(CH || SH) */ + if (tls->neg.hash == paze_hash_sha384()) { + paze_tls_tr_init(&tls->transcript, paze_hash_sha384()); + if (tls->ch1) paze_tls_tr_update(&tls->transcript, tls->ch1, tls->ch1_len); + if (sh_raw) paze_tls_tr_update(&tls->transcript, sh_raw, sh_raw_len); + } /* Parse SH extensions */ int found_v = 0, found_ks = 0; @@ -415,7 +445,7 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { while (tls->hs_state == PAZE_HS_CL_WAIT_EE) { const uint8_t *body; size_t blen; uint8_t type; - st = recv_hs(tls, &body, &blen, &type); + st = recv_hs(tls, &body, &blen, &type, NULL, NULL); if (st != PAZE_OK) return st; if (type != PAZE_TLS_HS_ENCRYPTED_EXTENSIONS) { paze_tls_set_err(tls, "expected EncryptedExtensions got %d", type); @@ -440,7 +470,8 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { while (tls->hs_state == PAZE_HS_CL_WAIT_CERT_CR_CV) { const uint8_t *body; size_t blen; uint8_t type; - st = recv_hs(tls, &body, &blen, &type); + const uint8_t *raw = NULL; size_t raw_len = 0; + st = recv_hs(tls, &body, &blen, &type, &raw, &raw_len); if (st != PAZE_OK) return st; if (type == PAZE_TLS_HS_CERTIFICATE_REQUEST) continue; @@ -485,11 +516,15 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { if (blen != 4 + sig_len) return PAZE_ERR_FORMAT; st = verify_cv(tls, 1, alg, body + 4, sig_len, &tls->peer_chain[0]); if (st != PAZE_OK) { paze_tls_set_err(tls, "CertificateVerify failed"); return PAZE_ERR_VERIFY; } + /* 验证通过后计入 transcript(recv_hs 不自动计入 CV) */ + if (raw && raw_len) paze_tls_tr_update(&tls->transcript, raw, raw_len); continue; } if (type == PAZE_TLS_HS_FINISHED) { st = verify_finished(tls, 1, body, blen); if (st != PAZE_OK) { paze_tls_set_err(tls, "server Finished verify failed"); return PAZE_ERR_VERIFY; } + /* 验证通过后计入 transcript(recv_hs 不自动计入 Finished) */ + if (raw && raw_len) paze_tls_tr_update(&tls->transcript, raw, raw_len); tls->hs_state = PAZE_HS_CL_WAIT_FINISHED; break; } @@ -546,14 +581,22 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { while (tls->hs_state == PAZE_HS_INIT) { const uint8_t *body; size_t blen; uint8_t type; + const uint8_t *ch_raw = NULL; size_t ch_raw_len = 0; /* Initialize transcript BEFORE receiving ClientHello */ paze_tls_tr_init(&tls->transcript, paze_hash_sha256()); - st = recv_hs(tls, &body, &blen, &type); + st = recv_hs(tls, &body, &blen, &type, &ch_raw, &ch_raw_len); if (st != PAZE_OK) return st; if (type != PAZE_TLS_HS_CLIENT_HELLO) { paze_tls_set_err(tls, "expected ClientHello, got %d", type); return PAZE_ERR_PROTOCOL; } + /* 保存 ClientHello 原始消息(含 4 字节头):供协商 hash 重放 */ + if (!tls->ch1 && ch_raw && ch_raw_len > 0) { + tls->ch1 = (uint8_t *)malloc(ch_raw_len); + if (!tls->ch1) return PAZE_ERR_PLATFORM; + memcpy(tls->ch1, ch_raw, ch_raw_len); + tls->ch1_len = ch_raw_len; + } if (blen < 2 + 32 + 1) return PAZE_ERR_FORMAT; const uint8_t *bp = body; bp += 2 + 32; /* leg_ver + random */ @@ -570,11 +613,15 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { if (paze_load16_be(bp + j) == prefer[i]) { chosen_cipher = prefer[i]; break; } } } - if (!chosen_cipher) return PAZE_ERR_PROTOCOL; + if (!chosen_cipher) return PAZE_ERR_NOTSUP; /* 无 1.3 套件 → 回退 1.2 */ tls->neg.cipher = chosen_cipher; + tls->neg.version = PAZE_TLS_VERSION_1_3; /* 服务端协商为 1.3 */ paze_tls_cipher_params(chosen_cipher, &tls->neg.aead, &tls->neg.hash, &tls->neg.key_len, &tls->neg.hash_len); + /* 用协商 hash 重建 transcript;必须重放 ClientHello(recv_hs 已把它 + * 算入 SHA-256 transcript,而实际 transcript 用协商 hash 从头算) */ paze_tls_tr_init(&tls->transcript, tls->neg.hash); + if (tls->ch1) paze_tls_tr_update(&tls->transcript, tls->ch1, tls->ch1_len); bp += slen + 1 + 1; /* skip suites + comp_len + comp */ if ((size_t)(bp - body) + 2 > blen) return PAZE_ERR_FORMAT; @@ -673,21 +720,37 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { st = send_hs(tls, PAZE_TLS_HS_ENCRYPTED_EXTENSIONS, ee + 4, body_len); if (st != PAZE_OK) return st; - /* Certificate */ + /* Certificate:cfg->cert_der 是链式 blob([3字节长][DER] 逐条), + * 需转成 RFC 8446 certificate_list(每条目追加 2 字节空 extensions)。 */ if (cfg->cert_der == NULL || cfg->cert_der_len == 0) { paze_tls_set_err(tls, "no server certificate configured"); return PAZE_ERR_INVALID; } uint8_t cert_body[8192]; uint8_t *cp = cert_body; - *cp++ = 0x00; - if (cfg->cert_der_len > 0xFFFFFF || 1 + 3 + cfg->cert_der_len > sizeof(cert_body)) - return PAZE_ERR_OVERFLOW; - cp[0] = (uint8_t)(cfg->cert_der_len >> 16); - cp[1] = (uint8_t)(cfg->cert_der_len >> 8); - cp[2] = (uint8_t)(cfg->cert_der_len); - cp += 3; - memcpy(cp, cfg->cert_der, cfg->cert_der_len); cp += cfg->cert_der_len; + *cp++ = 0x00; /* certificate_request_context 长度 */ + uint8_t *list_len_ptr = cp; cp += 3; + size_t entries_len = 0; + const uint8_t *cb = cfg->cert_der; + size_t cbl = cfg->cert_der_len; + while (cbl >= 3) { + size_t cl = ((size_t)cb[0] << 16) | ((size_t)cb[1] << 8) | cb[2]; + cb += 3; cbl -= 3; + if (cl > cbl || cl > 0xFFFF || + cp + 3 + cl + 2 > cert_body + sizeof(cert_body)) + return PAZE_ERR_OVERFLOW; + cp[0] = (uint8_t)(cl >> 16); + cp[1] = (uint8_t)(cl >> 8); + cp[2] = (uint8_t)(cl); + cp += 3; + memcpy(cp, cb, cl); cp += cl; cb += cl; cbl -= cl; + cp[0] = 0; cp[1] = 0; cp += 2; /* CertificateEntry extensions: 空 */ + entries_len += 3 + cl + 2; + } + if (entries_len > 0xFFFFFF) return PAZE_ERR_OVERFLOW; + list_len_ptr[0] = (uint8_t)(entries_len >> 16); + list_len_ptr[1] = (uint8_t)(entries_len >> 8); + list_len_ptr[2] = (uint8_t)(entries_len); size_t cert_body_len = (size_t)(cp - cert_body); st = send_hs(tls, PAZE_TLS_HS_CERTIFICATE, cert_body, cert_body_len); if (st != PAZE_OK) return st; @@ -715,7 +778,7 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { while (tls->hs_state == PAZE_HS_SV_WAIT_FINISHED) { const uint8_t *body; size_t blen; uint8_t type; - st = recv_hs(tls, &body, &blen, &type); + st = recv_hs(tls, &body, &blen, &type, NULL, NULL); if (st != PAZE_OK) return st; if (type == PAZE_TLS_HS_CERTIFICATE) continue; if (type == PAZE_TLS_HS_CERTIFICATE_VERIFY) continue; diff --git a/src/ssl/tls_internal.h b/src/ssl/tls_internal.h index 439b020..e42f0c6 100644 --- a/src/ssl/tls_internal.h +++ b/src/ssl/tls_internal.h @@ -133,12 +133,22 @@ typedef enum { PAZE_HS_CL_WAIT_CERT_CR_CV, /* Certificate/CertificateRequest/CertificateVerify */ PAZE_HS_CL_WAIT_FINISHED, PAZE_HS_CL_DONE, + /* client 1.2 */ + PAZE_HS_CL12_WAIT_SHD, /* 已发 CH,等 ServerHello */ + PAZE_HS_CL12_WAIT_CERT_SKE_SHD, /* 等 Certificate/SKE/ServerHelloDone */ + PAZE_HS_CL12_SEND_CKE, /* 发 CKE + CCS + Finished */ + PAZE_HS_CL12_WAIT_CCS_FIN, /* 等 CCS + Finished */ /* server 1.3 */ PAZE_HS_SV_WAIT_CH, PAZE_HS_SV_SEND_SH, PAZE_HS_SV_SEND_EE_CERT_CV_FIN, PAZE_HS_SV_WAIT_FINISHED, PAZE_HS_SV_DONE, + /* server 1.2 */ + PAZE_HS_SV12_SEND_SH_CERT_SKE_SHD, + PAZE_HS_SV12_WAIT_CKE, + PAZE_HS_SV12_WAIT_CCS_FIN, + PAZE_HS_SV12_SEND_CCS_FIN, /* 通用 */ PAZE_HS_ERROR } paze_tls_hs_state_t; @@ -206,10 +216,26 @@ struct paze_tls { paze_tls_hs_state_t hs_state; int handshake_done; - /* 服务端:peer(客户端)的 ClientHello 原始(用于 HelloRetryRequest) */ + /* 服务端:peer(客户端)的 ClientHello 原始(用于 HelloRetryRequest / TLS 1.2 回退) */ uint8_t *ch1; size_t ch1_len; int hello_retry; + /* 客户端:TLS 1.2 回退时保存的 ServerHello 原始消息 */ + uint8_t *sh1; size_t sh1_len; + + /* TLS 1.2 握手随机数 */ + uint8_t client_random[32]; + uint8_t server_random[32]; + /* TLS 1.2 master secret */ + uint8_t ms12[48]; + int ms12_valid; + /* TLS 1.2 pre-master secret(阶段间缓存) */ + uint8_t pms12[64]; + size_t pms12_len; + /* TLS 1.2 ECDHE:对端(ServerKeyExchange)公钥 */ + uint8_t ske_pub[65]; + size_t ske_pub_len; + /* 对端证书链(已解析) */ paze_x509_cert_t peer_chain[PAZE_TLS_MAX_CERTS]; int peer_chain_len; @@ -339,6 +365,11 @@ paze_status_t paze_tls_kex_ecdh(paze_tls_kex_t *mine, uint16_t server_group, /* 1.3 握手 (由 tls_client/tls_server 调用) */ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls); paze_status_t paze_tls13_server_handshake(paze_tls_t *tls); +/* 构造兼容 CH(TLS 1.3 + 1.2 套件/扩展)。TLS 1.2 独立模式复用。 */ +paze_status_t paze_tls13_build_ch(paze_tls_t *tls, uint8_t *out, size_t *outlen); +/* TLS 1.2 握手 */ +paze_status_t paze_tls12_client_handshake(paze_tls_t *tls); +paze_status_t paze_tls12_server_handshake(paze_tls_t *tls); /* ============================================================ * 便利 diff --git a/src/ssl/tls_record.c b/src/ssl/tls_record.c index acd09ea..70a8055 100644 --- a/src/ssl/tls_record.c +++ b/src/ssl/tls_record.c @@ -15,8 +15,8 @@ * 解密后从末尾剥离 zeros 与 content_type 得真实 fragment。 * * 1.2 AEAD-GCM (RFC 5288): - * nonce = explicit_nonce(8) || fixed_iv(4) - * aad = seq(8) || type(1) || version(2) || length(2) ; length = ct_len + 16 + * nonce = fixed_iv(4) || explicit_nonce(8) ; GCMNonce = salt || nonce_explicit + * aad = seq(8) || type(1) || version(2) || length(2) ; length = 明文长度(ct_len) * record body = explicit_nonce(8) || ciphertext || tag(16) * fixed_iv 取自 key material 的 write_IV(4 字节),存于 traffic_key.iv[0..3]。 * @@ -76,10 +76,14 @@ paze_status_t paze_tls_record_read(paze_tls_t *tls, uint8_t *body = hdr + 5; size_t total = 5 + rlen; - /* ChangeCipherSpec(1.3 兼容占位):直接消费并继续 */ + /* ChangeCipherSpec:1.2 需在此切换密钥;1.3 由握手层忽略。 + * 不在此静默跳过,返回给调用方(recv_hs / tls12 状态机处理)。 */ if (rtype == PAZE_TLS_CT_CHANGE_CIPHER_SPEC) { + *content_type = rtype; + *out = body; + *outlen = rlen; tls->in_off += total; - continue; + return PAZE_OK; } /* 明文 record(握手初期 / Alert) */ @@ -130,14 +134,16 @@ paze_status_t paze_tls_record_read(paze_tls_t *tls, if (rlen < 8 + 16) { paze_tls_set_err(tls, "short 1.2 record"); return PAZE_ERR_PROTOCOL; } size_t ctlen = rlen - 8 - 16; uint8_t nonce[12]; - memcpy(nonce, body, 8); - memcpy(nonce + 8, k->iv, 4); /* fixed_iv 在 iv 前 4 字节 */ - /* aad = seq(8) || type(1) || ver(2) || length(2) ; length = ctlen + 16 */ + /* RFC 5288 §3 GCMNonce = salt(4) || nonce_explicit(8),salt 在前 */ + memcpy(nonce, k->iv, 4); /* fixed_iv = client/server_write_IV */ + memcpy(nonce + 4, body, 8); + /* aad = seq(8) || type(1) || ver(2) || length(2) ; length = TLSCompressed.length = ctlen + * (RFC 5246 §6.2.3.3 / RFC 5288 §3:明文长度,非 ct+tag;1.3 才是 ct+16) */ uint8_t aad[13]; uint64_t seq = k->seq; for (int j = 0; j < 8; j++) aad[j] = (uint8_t)(seq >> (56 - 8 * j)); aad[8] = rtype; aad[9] = 0x03; aad[10] = 0x03; - size_t inner_len = ctlen + 16; + size_t inner_len = ctlen; aad[11] = (uint8_t)(inner_len >> 8); aad[12] = (uint8_t)(inner_len); uint8_t tag[16]; memcpy(tag, body + 8 + ctlen, 16); @@ -149,7 +155,10 @@ paze_status_t paze_tls_record_read(paze_tls_t *tls, rs = paze_chacha20poly1305_decrypt(k->key, nonce, aad, 13, body + 8, ctlen, tag, tls->app_buf); } - if (rs != PAZE_OK) { paze_tls_set_err(tls, "bad 1.2 record MAC"); return PAZE_ERR_VERIFY; } + if (rs != PAZE_OK) { + paze_tls_set_err(tls, "bad 1.2 record MAC"); + return PAZE_ERR_VERIFY; + } k->seq++; *content_type = rtype; *out = tls->app_buf; @@ -221,13 +230,14 @@ paze_status_t paze_tls_record_send(paze_tls_t *tls, uint8_t content_type, for (int j = 0; j < 8; j++) enonce[j] = (uint8_t)(seq >> (56 - 8 * j)); memcpy(o + off, enonce, 8); off += 8; uint8_t nonce[12]; - memcpy(nonce, enonce, 8); - memcpy(nonce + 8, k->iv, 4); /* fixed_iv */ - size_t inner_len = ctlen + 16; + /* RFC 5288 §3 GCMNonce = salt(4) || nonce_explicit(8),salt 在前 */ + memcpy(nonce, k->iv, 4); /* fixed_iv */ + memcpy(nonce + 4, enonce, 8); + /* aad length = TLSCompressed.length = 明文长度(RFC 5246 §6.2.3.3) */ uint8_t aad[13]; for (int j = 0; j < 8; j++) aad[j] = (uint8_t)(seq >> (56 - 8 * j)); aad[8] = content_type; aad[9] = 0x03; aad[10] = 0x03; - aad[11] = (uint8_t)(inner_len >> 8); aad[12] = (uint8_t)(inner_len); + aad[11] = (uint8_t)(ctlen >> 8); aad[12] = (uint8_t)(ctlen); uint8_t tag[16]; paze_status_t st; if (k->kind == PAZE_AEAD_AES_128_GCM || k->kind == PAZE_AEAD_AES_256_GCM) {