From 3e8a5442fa0a5cb2b0a1df4ee3adb5fe75007a4b Mon Sep 17 00:00:00 2001 From: JGZYES Date: Thu, 13 Aug 2026 19:30:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20TLS=201.3=20PSK=20?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E6=81=A2=E5=A4=8D,=E6=9E=84=E5=BB=BA?= =?UTF-8?q?=E8=BE=93=E5=87=BA=E7=A7=BB=E8=87=B3=20bin/=20=E5=B9=B6?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20standalone/psftp=20=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TLS 1.3 PSK 会话恢复: NewSessionTicket 签发/解析、ticket+binder 校验、selected_identity 回选、恢复握手免证书(pazessl -sess_in/-sess_out 端到端验证); 构建脚本输出 bin/ 并支持独立命令与 psftp; 修复 SFTP 二进制传输与 copy_id -P; 补充 tests/ 调试与 verify_tls --- README.md | 2 + STATUS.md | 5 +- apps/copy_id/main.c | 4 +- apps/pazessl/main.c | 123 +++++-- apps/psftp/main.c | 709 ++++++++++++++++++++++++++++++++++++++ apps/pssh/standalone.c | 65 ++++ build.ps1 | 27 +- build/run_tls_test.ps1 | 37 +- down.bin | 4 + include/paze/tls.h | 31 ++ src/crypto/x509.c | 2 +- src/ssh/sftp_server.c | 1 + src/ssl/tls_conn.c | 57 +++ src/ssl/tls_extensions.c | 103 ++++++ src/ssl/tls_handshake13.c | 357 +++++++++++++++++-- src/ssl/tls_internal.h | 60 ++++ src/ssl/tls_keys.c | 70 +++- tests/dbg_curve.c | 71 ++++ tests/dbg_div2.c | 70 ++++ tests/dbg_div3.c | 78 +++++ tests/dbg_div4.c | 79 +++++ tests/dbg_divmod.c | 84 +++++ tests/dbg_ecdsa.c | 31 ++ tests/dbg_ecdsa_cert.c | 81 +++++ tests/dbg_mod.c | 86 +++++ tests/dbg_modexp.c | 77 +++++ tests/dbg_p2.c | 23 ++ tests/dbg_point.c | 65 ++++ tests/dbg_point2.c | 47 +++ tests/dbg_rfc6979.c | 79 +++++ tests/dbg_x25519.c | 27 ++ tests/verify_tls.c | 80 +++++ up.bin | 2 + 33 files changed, 2565 insertions(+), 72 deletions(-) create mode 100644 apps/psftp/main.c create mode 100644 apps/pssh/standalone.c create mode 100644 down.bin create mode 100644 tests/dbg_curve.c create mode 100644 tests/dbg_div2.c create mode 100644 tests/dbg_div3.c create mode 100644 tests/dbg_div4.c create mode 100644 tests/dbg_divmod.c create mode 100644 tests/dbg_ecdsa.c create mode 100644 tests/dbg_ecdsa_cert.c create mode 100644 tests/dbg_mod.c create mode 100644 tests/dbg_modexp.c create mode 100644 tests/dbg_p2.c create mode 100644 tests/dbg_point.c create mode 100644 tests/dbg_point2.c create mode 100644 tests/dbg_rfc6979.c create mode 100644 tests/dbg_x25519.c create mode 100644 tests/verify_tls.c create mode 100644 up.bin diff --git a/README.md b/README.md index c7ad044..628ce2c 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,5 @@ cmake --build build -j ## 状态 工程量巨大,按层推进。当前完成度见 `STATUS.md`。 + +TLS 1.3 已覆盖:完整握手(ECDHE + X25519/P-256)、1.3↔1.2 版本回退、**PSK 会话恢复**(NewSessionTicket 签发/解析、binder 校验、恢复握手免证书),并通过 OpenSSL `s_server`/`s_client` 双向互操作对测。 diff --git a/STATUS.md b/STATUS.md index 8c093d6..09e7ccd 100644 --- a/STATUS.md +++ b/STATUS.md @@ -24,11 +24,12 @@ ## PazeSSL — TLS(🔶 部分完成) - ✅ TLS 1.3 客户端/服务端状态机(record、handshake、key schedule、扩展) +- ✅ TLS 1.3 **PSK 会话恢复**(RFC 8446 §4.2.11 / §4.6.1):NewSessionTicket 签发与解析、ticket+binder 校验、selected_identity 回选、恢复握手跳过证书;`pazessl -sess_in/-sess_out` 端到端验证通过 - ✅ TLS 1.2 客户端/服务端状态机 + 1.3↔1.2 版本回退(ECDHE-RSA/ECDSA/RSA AES-GCM、CHACHA20-POLY1305) - ✅ OpenSSL 互操作对测 4/4:s_server↔s_client 双向 TLS 1.3 / TLS 1.2(`build/run_openssl_interop.ps1`) - ✅ 证书链与信任锚加载、RSA/ECDSA 私钥 PEM 解析 - ⬜ TLS 1.3 服务端 SHA-384 密码套件握手重放(SHA-256 已覆盖) -- ⬜ PSK / 0-RTT / HRR 实际数据路径 +- ⬜ 0-RTT / HRR 数据路径 ## PazeSSH — SSH-2(✅ 完成) @@ -68,4 +69,4 @@ ## 遗留项(已知) - X.509 ECDSA 证书解析仍失败(P-256 曲线点验证) -- TLS 1.3 SHA-384 服务端握手重放、PSK/0-RTT 未覆盖 +- TLS 1.3 SHA-384 服务端握手重放、0-RTT / HRR 未覆盖 diff --git a/apps/copy_id/main.c b/apps/copy_id/main.c index 374c762..59b92d3 100644 --- a/apps/copy_id/main.c +++ b/apps/copy_id/main.c @@ -103,7 +103,7 @@ int pssh_cmd_copyid(int argc, char **argv) { for (int i = 1; i < argc; i++) { if ((strcmp(argv[i], "-i") == 0) && i + 1 < argc) keyfile = argv[++i]; - else if ((strcmp(argv[i], "-p") == 0) && i + 1 < argc) port = atoi(argv[++i]); + else if ((strcmp(argv[i], "-p") == 0 || strcmp(argv[i], "-P") == 0) && i + 1 < argc) port = atoi(argv[++i]); else if (strcmp(argv[i], "-pwd") == 0 && i + 1 < argc) password_arg = argv[++i]; else if (argv[i][0] == '-') continue; else { @@ -118,7 +118,7 @@ int pssh_cmd_copyid(int argc, char **argv) { } } if (!host) { - fprintf(stderr, "Usage: paze-copy-id [-i keyfile] [-p port] [-pwd password] [user@]host\n"); + fprintf(stderr, "Usage: paze-copy-id [-i keyfile] [-p/-P port] [-pwd password] [user@]host\n"); return 1; } if (!user) { diff --git a/apps/pazessl/main.c b/apps/pazessl/main.c index 32a553f..3605095 100644 --- a/apps/pazessl/main.c +++ b/apps/pazessl/main.c @@ -97,6 +97,22 @@ static uint8_t *read_file(const char *path, size_t *out_len) { return buf; } +/* TLS 1.3 session 持久化(二进制 dump,自用格式) */ +static int session_write_file(const char *path, const paze_tls_session_t *s) { + FILE *f = fopen(path, "wb"); + if (!f) return -1; + fwrite(s, 1, sizeof(*s), f); + fclose(f); + return 0; +} +static int session_read_file(const char *path, paze_tls_session_t *s) { + FILE *f = fopen(path, "rb"); + if (!f) return -1; + size_t n = fread(s, 1, sizeof(*s), f); + fclose(f); + return n == sizeof(*s) ? 0 : -1; +} + int main(int argc, char **argv) { if (argc < 2) return usage(); const char *cmd = argv[1]; @@ -106,6 +122,8 @@ int main(int argc, char **argv) { const char *host = NULL; int port = 443; const char *cafile = NULL; const char *sni = NULL; + const char *sess_in = NULL, *sess_out = NULL; + const char *msg = NULL; int only_12 = 0; for (int i = 2; i < argc; i++) { if (strcmp(argv[i], "-connect") == 0 && i + 1 < argc) { @@ -117,6 +135,12 @@ 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], "-sess_in") == 0 && i + 1 < argc) { + sess_in = argv[++i]; + } else if (strcmp(argv[i], "-sess_out") == 0 && i + 1 < argc) { + sess_out = argv[++i]; + } else if (strcmp(argv[i], "-msg") == 0 && i + 1 < argc) { + msg = argv[++i]; } else if (strcmp(argv[i], "-tls1_2") == 0) { only_12 = 1; } @@ -136,6 +160,11 @@ int main(int argc, char **argv) { free(cdata); } } + if (sess_in) { + paze_tls_session_t ss; + if (session_read_file(sess_in, &ss) == 0 && ss.valid) + paze_tls_config_set_session(cfg, &ss); + } sock_t sock = tcp_connect(host, port); if (sock == SOCK_INVALID) { fprintf(stderr, "connect failed\n"); return 1; } @@ -154,16 +183,32 @@ int main(int argc, char **argv) { paze_tls_free(tls); closesocket(sock); paze_tls_config_free(cfg); return 1; } - fprintf(stderr, "Connected (TLS %04x, cipher %04x)\n", + fprintf(stderr, "Connected (TLS %04x, cipher %04x, resumed %s)\n", (unsigned)paze_tls_get_version(tls), - (unsigned)paze_tls_get_cipher_suite(tls)); + (unsigned)paze_tls_get_cipher_suite(tls), + paze_tls_session_resumed(tls) ? "YES" : "no"); + if (sess_out) { + paze_tls_session_t ss; + if (paze_tls_get_session(tls, &ss) == PAZE_OK && ss.valid) { + session_write_file(sess_out, &ss); + fprintf(stderr, "Session saved -> %s\n", sess_out); + } else { + fprintf(stderr, "No session received\n"); + } + } - /* Simple stdio relay */ uint8_t buf[16384]; - for (;;) { + if (msg) { + paze_tls_write(tls, (const uint8_t *)msg, strlen(msg)); int n = paze_tls_read(tls, buf, sizeof(buf)); - if (n > 0) fwrite(buf, 1, (size_t)n, stdout); - if (n <= 0) break; + if (n > 0) { fwrite(buf, 1, (size_t)n, stdout); fprintf(stderr, "\n"); } + } else { + /* Simple stdio relay */ + for (;;) { + int n = paze_tls_read(tls, buf, sizeof(buf)); + if (n > 0) fwrite(buf, 1, (size_t)n, stdout); + if (n <= 0) break; + } } paze_tls_close(tls); paze_tls_free(tls); @@ -303,10 +348,12 @@ int main(int argc, char **argv) { int port = 4433; const char *certf = NULL; const char *keyf = NULL; int only_12 = 0; + int reuse = 1; 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], "-reuse") == 0 && i + 1 < argc) reuse = atoi(argv[++i]); else if (strcmp(argv[i], "-tls1_2") == 0) only_12 = 1; } if (!certf || !keyf) { @@ -344,44 +391,54 @@ int main(int argc, char **argv) { fprintf(stderr, "bind port %d failed\n", port); closesocket(lsock); return 1; } - if (listen(lsock, 1) < 0) { + if (listen(lsock, 4) < 0) { fprintf(stderr, "listen failed\n"); closesocket(lsock); return 1; } - fprintf(stderr, "Listening on port %d...\n", port); + fprintf(stderr, "Listening on port %d (-reuse %d)...\n", port, reuse); - struct sockaddr_in client; - socklen_t cl = sizeof(client); - sock_t conn = accept(lsock, (struct sockaddr *)&client, &cl); - if (conn == SOCK_INVALID) { closesocket(lsock); return 1; } - fprintf(stderr, "Client connected\n"); + int overall = 0; + for (int conn_idx = 0; conn_idx < reuse; conn_idx++) { + struct sockaddr_in client; + socklen_t cl = sizeof(client); + sock_t conn = accept(lsock, (struct sockaddr *)&client, &cl); + if (conn == SOCK_INVALID) { closesocket(lsock); return 1; } + fprintf(stderr, "Client #%d connected\n", conn_idx + 1); - paze_tls_t *tls = paze_tls_new(cfg); - paze_tls_bio_t bio = { &conn, sock_read, sock_write }; - paze_tls_set_bio(tls, bio); + paze_tls_t *tls = paze_tls_new(cfg); + paze_tls_bio_t bio = { &conn, sock_read, sock_write }; + paze_tls_set_bio(tls, bio); - paze_status_t st = paze_tls_handshake(tls); - if (st != PAZE_OK) { - fprintf(stderr, "TLS handshake failed: %s\n", paze_tls_last_error(tls)); - } else { - fprintf(stderr, "TLS handshake OK (version %04x, cipher %04x)\n", - (unsigned)paze_tls_get_version(tls), - (unsigned)paze_tls_get_cipher_suite(tls)); - /* Echo loop */ - uint8_t buf[4096]; - for (int i = 0; i < 100; i++) { - int n = paze_tls_read(tls, buf, sizeof(buf)); - if (n <= 0) break; - paze_tls_write(tls, buf, (size_t)n); + paze_status_t st = paze_tls_handshake(tls); + if (st != PAZE_OK) { + fprintf(stderr, "TLS handshake failed: %s\n", paze_tls_last_error(tls)); + } else { + fprintf(stderr, "TLS handshake OK (version %04x, cipher %04x, resumed %s)\n", + (unsigned)paze_tls_get_version(tls), + (unsigned)paze_tls_get_cipher_suite(tls), + paze_tls_session_resumed(tls) ? "YES" : "no"); + /* 跨连接共享 session ticket key,使后续连接可恢复会话 */ + uint8_t tk[32]; int tk_ready = 0; + paze_tls_get_session_ticket_key(tls, tk, &tk_ready); + if (tk_ready) paze_tls_config_set_session_ticket_key(cfg, tk); + /* Echo loop */ + uint8_t buf[4096]; + for (int i = 0; i < 100; i++) { + int n = paze_tls_read(tls, buf, sizeof(buf)); + if (n <= 0) break; + paze_tls_write(tls, buf, (size_t)n); + } + overall = 0; } + paze_tls_close(tls); + paze_tls_free(tls); + closesocket(conn); + if (st != PAZE_OK) overall = 1; } - paze_tls_close(tls); - paze_tls_free(tls); - closesocket(conn); closesocket(lsock); paze_tls_config_free(cfg); free(cdata); free(kdata); - return st == PAZE_OK ? 0 : 1; + return overall; } fprintf(stderr, "pazessl %s: not yet implemented\n", cmd); diff --git a/apps/psftp/main.c b/apps/psftp/main.c new file mode 100644 index 0000000..010c560 --- /dev/null +++ b/apps/psftp/main.c @@ -0,0 +1,709 @@ +/* psftp —— 独立 SFTP 客户端 (平替 OpenSSH sftp) + * + * 用法: + * psftp [选项] [user@]host[:初始路径] + * -P 远程端口 (默认 22) + * -pwd 密码 + * -i 私钥文件 (publickey 认证) + * -o StrictHostKeyChecking=no|accept-new|yes + * -b 批处理:逐行执行命令后退出 + * + * 交互命令: + * bye/exit/quit 退出 + * pwd/lpwd 显示远端/本地当前目录 + * cd/lcd 切换远端/本地目录 + * ls [path] 列出远端目录 + * lls [path] 列出本地目录 + * get [-r] [local] 下载(文件或目录) + * put [-r] [remote] 上传(文件或目录) + * rm 删除远端文件 + * mkdir 创建远端目录 + * rmdir 删除远端空目录 + * rename 远端重命名 + * help/? + */ +#include "paze/ssh.h" +#include "paze/ssh_agent.h" +#include +#include +#include +#include + +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif + +/* SFTP attrs 权限值中的文件类型掩码(与 POSIX st_mode 高 4 位一致) */ +#define SSH_S_IFMT 0170000 +#define SSH_S_IFDIR 0040000 + +#ifdef _WIN32 +#include +#include +#include +#define GETCH _getch +#define IS_SEP(c) ((c) == '/' || (c) == '\\') +static int local_mkdir(const char *p) { return _mkdir(p); } +#else +#include +#include +#define IS_SEP(c) ((c) == '/') +static int local_mkdir(const char *p) { return mkdir(p, 0755); } +#endif + +#define SFTP_BUF 32768 + +static void path_join_local(char *out, size_t n, const char *a, const char *b) { + size_t al = strlen(a); + int need = (al > 0 && !IS_SEP(a[al - 1])) ? 1 : 0; +#ifdef _WIN32 + snprintf(out, n, need ? "%s\\%s" : "%s%s", a, b); +#else + snprintf(out, n, need ? "%s/%s" : "%s%s", a, b); +#endif +} +static void rjoin(char *out, size_t n, const char *base, const char *name) { + size_t bl = strlen(base); + int need = (bl > 0 && base[bl - 1] != '/') ? 1 : 0; + snprintf(out, n, need ? "%s/%s" : "%s%s", base, name); +} + +static const char *path_basename(const char *p) { + const char *last = p; + size_t len = strlen(p); + while (len > 1 && IS_SEP(p[len - 1])) len--; + for (size_t i = 0; i < len; i++) + if (IS_SEP(p[i])) last = p + i + 1; + return last; +} + +typedef struct { char name[512]; } ldir_ent_t; +static int local_dir_enum(const char *dir, ldir_ent_t *ents, int max) { + int n = 0; +#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 + snprintf(pat, sizeof(pat), "%s\\*", dir); + WIN32_FIND_DATAA fd; + HANDLE h = FindFirstFileA(pat, &fd); + if (h == INVALID_HANDLE_VALUE) return -1; + do { + if (strcmp(fd.cFileName, ".") == 0 || strcmp(fd.cFileName, "..") == 0) + continue; + if (n < max) { snprintf(ents[n].name, sizeof(ents[n].name), "%s", fd.cFileName); n++; } + } while (FindNextFileA(h, &fd)); + FindClose(h); +#else + DIR *d = opendir(dir); + if (!d) return -1; + struct dirent *e; + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) + continue; + if (n < max) { snprintf(ents[n].name, sizeof(ents[n].name), "%s", e->d_name); n++; } + } + closedir(d); +#endif + return n; +} + +/* ============================================================ + * 连接 + 认证 + * ============================================================ */ +typedef struct { + int port; + const char *user, *password_arg, *keyfile; + int hostkey_mode; +} cli_cfg_t; + +static ssh_session_t *connect_session(const cli_cfg_t *cfg, + const char *host, const char *ruser) { + const char *user = ruser ? ruser : cfg->user; + if (!user) { + const char *envu = getenv("USER"); + if (!envu) envu = getenv("USERNAME"); + user = envu ? envu : "root"; + } + + char pw_buf[256]; + const char *password = cfg->password_arg; + if (!password) { + fprintf(stderr, "Password: "); + int idx = 0, ch; + while (idx < (int)sizeof(pw_buf) - 1) { + ch = GETCH(); + if (ch == '\r' || ch == '\n') break; + if (ch == '\b' || ch == 0x7f) { if (idx > 0) { idx--; fprintf(stderr, "\b \b"); } continue; } + if (ch < 32) continue; + pw_buf[idx++] = (char)ch; + fprintf(stderr, "*"); + } + pw_buf[idx] = '\0'; + fprintf(stderr, "\n"); + password = pw_buf; + } + + ssh_session_t *s = ssh_session_new(0); + if (!s) { fprintf(stderr, "psftp: 会话分配失败\n"); return NULL; } + ssh_session_set_hostkey_check(s, cfg->hostkey_mode); + if (ssh_client_connect(s, host, (uint16_t)cfg->port) < 0) { + fprintf(stderr, "psftp: 连接 %s 失败\n", host); + ssh_session_free(s); + return NULL; + } + + uint8_t *keydata = NULL; size_t keylen = 0; + if (cfg->keyfile) { + FILE *kf = fopen(cfg->keyfile, "rb"); + if (kf) { + fseek(kf, 0, SEEK_END); + long sz = ftell(kf); + rewind(kf); + if (sz > 0 && sz < 4 * 1024 * 1024) { + keydata = (uint8_t *)malloc((size_t)sz); + if (keydata && fread(keydata, 1, (size_t)sz, kf) != (size_t)sz) { + free(keydata); keydata = NULL; + } else { + keylen = (size_t)sz; + } + } + fclose(kf); + } + } + + int auth_ok = 0; + if (keydata) { + auth_ok = ssh_auth_client_loop(s, user, password, keydata, keylen) == 0; + free(keydata); + } else { + ssh_agent_t *agent = NULL; + if (ssh_agent_connect(&agent) == 0) { + auth_ok = ssh_auth_client_agent_loop(s, user, password, agent) == 0; + ssh_agent_close(agent); + } + if (!auth_ok) + auth_ok = ssh_auth_client_loop(s, user, password, NULL, 0) == 0; + } + if (!auth_ok) { + fprintf(stderr, "psftp: 认证失败\n"); + ssh_session_free(s); + return NULL; + } + return s; +} + +/* ============================================================ + * 传输 (文件 / 目录递归) + * ============================================================ */ +static int sftp_get_file(ssh_sftp_t *sf, const char *remote, + const char *local) { + uint8_t handle[256]; + size_t hlen = 0; + if (ssh_sftp_open(sf, remote, SSH_FXF_READ, handle, &hlen) < 0) { + fprintf(stderr, "psftp: 无法打开远端 %s\n", remote); + return -1; + } + FILE *f = fopen(local, "wb"); + if (!f) { + fprintf(stderr, "psftp: 无法创建本地 %s\n", local); + ssh_sftp_close(sf, handle, hlen); + return -1; + } + uint64_t off = 0; + int code = 0, rc = 0; + for (;;) { + uint8_t buf[SFTP_BUF]; + int r = ssh_sftp_read(sf, handle, hlen, off, buf, sizeof(buf), &code); + if (r < 0) { fprintf(stderr, "psftp: 读取 %s 失败 (code=%d)\n", remote, code); rc = -1; break; } + if (r == 0) break; + if (fwrite(buf, 1, (size_t)r, f) != (size_t)r) { rc = -1; break; } + off += (uint64_t)r; + } + fclose(f); + ssh_sftp_close(sf, handle, hlen); + return rc; +} + +static int sftp_put_file(ssh_sftp_t *sf, const char *local, + const char *remote) { + FILE *f = fopen(local, "rb"); + if (!f) { fprintf(stderr, "psftp: 无法打开本地 %s\n", local); return -1; } + uint8_t handle[256]; + size_t hlen = 0; + if (ssh_sftp_open(sf, remote, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC, + handle, &hlen) < 0) { + fprintf(stderr, "psftp: 无法在远端创建 %s\n", remote); + fclose(f); + return -1; + } + uint64_t off = 0; + uint8_t buf[SFTP_BUF]; + size_t n; + int rc = 0; + while ((n = fread(buf, 1, sizeof(buf), f)) > 0) { + size_t done = 0; + while (done < n) { + int w = ssh_sftp_write(sf, handle, hlen, off + done, buf + done, + (uint32_t)(n - done), NULL); + if (w <= 0) { rc = -1; break; } + done += (size_t)w; + } + if (rc < 0) break; + off += n; + } + fclose(f); + ssh_sftp_close(sf, handle, hlen); + return rc; +} + +static void remote_mkdir_p(ssh_sftp_t *sf, const char *path) { + char tmp[2048]; + size_t plen = strlen(path); + if (plen >= sizeof(tmp)) return; + const char *last = strrchr(path, '/'); + if (last && last != path) { + size_t pl = (size_t)(last - path); + if (pl >= sizeof(tmp)) return; + memcpy(tmp, path, pl); + tmp[pl] = '\0'; + remote_mkdir_p(sf, tmp); + } + if (ssh_sftp_mkdir(sf, path, 0755) < 0) { + ssh_sftp_ent_t e; + if (ssh_sftp_stat(sf, path, &e) == 0 && + (e.perms & SSH_S_IFMT) == SSH_S_IFDIR) + return; + } +} + +static int sftp_get_dir(ssh_sftp_t *sf, const char *remote, + const char *local) { + local_mkdir(local); + uint8_t handle[256]; + size_t hlen = 0; + if (ssh_sftp_opendir(sf, remote, handle, &hlen) < 0) { + fprintf(stderr, "psftp: 无法打开远端目录 %s\n", remote); + return -1; + } + ssh_sftp_ent_t ent; + int rc = 0; + while (ssh_sftp_readdir(sf, handle, hlen, &ent) > 0) { + if (strcmp(ent.name, ".") == 0 || strcmp(ent.name, "..") == 0) + continue; + char lp[1024], rp[2048]; + path_join_local(lp, sizeof(lp), local, ent.name); + rjoin(rp, sizeof(rp), remote, ent.name); + if ((ent.perms & SSH_S_IFMT) == SSH_S_IFDIR) { + if (sftp_get_dir(sf, rp, lp) < 0) rc = -1; + } else if (sftp_get_file(sf, rp, lp) < 0) { + rc = -1; + } + } + ssh_sftp_close(sf, handle, hlen); + return rc; +} + +static int sftp_put_dir(ssh_sftp_t *sf, const char *local, + const char *remote) { + remote_mkdir_p(sf, remote); + ldir_ent_t *ents = (ldir_ent_t *)malloc(1024 * sizeof(ldir_ent_t)); + if (!ents) return -1; + int n = local_dir_enum(local, ents, 1024); + if (n < 0) { free(ents); fprintf(stderr, "psftp: 无法读取本地目录 %s\n", local); return -1; } + int rc = 0; + for (int i = 0; i < n; i++) { + char lp[1024], rp[2048]; + path_join_local(lp, sizeof(lp), local, ents[i].name); + rjoin(rp, sizeof(rp), remote, ents[i].name); + struct stat st; + if (stat(lp, &st) != 0) { rc = -1; continue; } + if (S_ISDIR(st.st_mode)) { + if (sftp_put_dir(sf, lp, rp) < 0) rc = -1; + } else if (sftp_put_file(sf, lp, rp) < 0) { + rc = -1; + } + } + free(ents); + return rc; +} + +/* ============================================================ + * 交互命令 + * ============================================================ */ +typedef struct { + ssh_sftp_t *sf; + char rdir[2048]; + char ldir[1024]; +} psftp_t; + +static void cmd_pwd(psftp_t *p) { printf("远端当前目录: %s\n", p->rdir); } +static void cmd_lpwd(psftp_t *p) { +#ifdef _WIN32 + if (GetCurrentDirectoryA(sizeof(p->ldir), p->ldir)) printf("本地当前目录: %s\n", p->ldir); +#else + if (getcwd(p->ldir, sizeof(p->ldir))) printf("本地当前目录: %s\n", p->ldir); +#endif +} + +static void cmd_cd(psftp_t *p, const char *path) { + char out[2048]; + if (ssh_sftp_realpath(p->sf, path, out, sizeof(out)) < 0) { + fprintf(stderr, "psftp: cd: %s: 无此目录\n", path); + return; + } + ssh_sftp_ent_t e; + if (ssh_sftp_stat(p->sf, out, &e) < 0 || + (e.perms & SSH_S_IFMT) != SSH_S_IFDIR) { + fprintf(stderr, "psftp: cd: %s: 无此目录\n", path); + return; + } + snprintf(p->rdir, sizeof(p->rdir), "%s", out); +} + +static void cmd_lcd(psftp_t *p, const char *path) { +#ifdef _WIN32 + if (SetCurrentDirectoryA(path)) { + GetCurrentDirectoryA(sizeof(p->ldir), p->ldir); + printf("本地现在使用: %s\n", p->ldir); + } else fprintf(stderr, "psftp: lcd: %s: 无此目录\n", path); +#else + if (chdir(path) == 0) { + getcwd(p->ldir, sizeof(p->ldir)); + printf("本地现在使用: %s\n", p->ldir); + } else fprintf(stderr, "psftp: lcd: %s: 无此目录\n", path); +#endif +} + +static void cmd_ls(psftp_t *p, const char *path) { + char rp[2048]; + if (!path || !path[0]) { + snprintf(rp, sizeof(rp), "%s", p->rdir); + } else if (path[0] == '/') { + snprintf(rp, sizeof(rp), "%s", path); + } else { + rjoin(rp, sizeof(rp), p->rdir, path); + } + uint8_t handle[256]; + size_t hlen = 0; + if (ssh_sftp_opendir(p->sf, rp, handle, &hlen) < 0) { + fprintf(stderr, "psftp: ls: %s: 无法打开\n", path ? path : "."); + return; + } + ssh_sftp_ent_t ent; + while (ssh_sftp_readdir(p->sf, handle, hlen, &ent) > 0) { + char t = ((ent.perms & SSH_S_IFMT) == SSH_S_IFDIR) ? 'd' : '-'; + printf("%c %10llu %s\n", t, (unsigned long long)ent.size, ent.name); + } + ssh_sftp_close(p->sf, handle, hlen); +} + +static void cmd_lls(psftp_t *p, const char *path) { + char lp[1024]; + if (!path || !path[0]) { + snprintf(lp, sizeof(lp), "%s", p->ldir); + } else if (IS_SEP(path[0])) { + snprintf(lp, sizeof(lp), "%s", path); + } else { + path_join_local(lp, sizeof(lp), p->ldir, path); + } + ldir_ent_t *ents = (ldir_ent_t *)malloc(1024 * sizeof(ldir_ent_t)); + if (!ents) return; + int n = local_dir_enum(lp, ents, 1024); + for (int i = 0; i < n; i++) printf("%s\n", ents[i].name); + free(ents); +} + +static void cmd_get(psftp_t *p, int recursive, const char *remote, + const char *local) { + char rp[2048]; + if (remote[0] == '/') snprintf(rp, sizeof(rp), "%s", remote); + else rjoin(rp, sizeof(rp), p->rdir, remote); + + char lp[1024]; + if (local && local[0]) { + if (IS_SEP(local[0])) snprintf(lp, sizeof(lp), "%s", local); + else path_join_local(lp, sizeof(lp), p->ldir, local); + } else { + path_join_local(lp, sizeof(lp), p->ldir, path_basename(rp)); + } + + ssh_sftp_ent_t e; + int is_dir = 0; + if (ssh_sftp_stat(p->sf, rp, &e) == 0) + is_dir = ((e.perms & SSH_S_IFMT) == SSH_S_IFDIR); + if (is_dir) { + if (!recursive) { fprintf(stderr, "psftp: %s 是目录,需要 -r\n", rp); return; } + if (sftp_get_dir(p->sf, rp, lp) < 0) fprintf(stderr, "psftp: get 目录失败\n"); + } else { + if (sftp_get_file(p->sf, rp, lp) < 0) fprintf(stderr, "psftp: get 失败\n"); + } +} + +static void cmd_put(psftp_t *p, int recursive, const char *local, + const char *remote) { + char lp[1024]; + if (IS_SEP(local[0])) snprintf(lp, sizeof(lp), "%s", local); + else path_join_local(lp, sizeof(lp), p->ldir, local); + + char rp[2048]; + if (remote && remote[0]) { + if (remote[0] == '/') snprintf(rp, sizeof(rp), "%s", remote); + else rjoin(rp, sizeof(rp), p->rdir, remote); + } else { + rjoin(rp, sizeof(rp), p->rdir, path_basename(lp)); + } + + struct stat st; + if (stat(lp, &st) != 0) { fprintf(stderr, "psftp: put: %s: 无此文件\n", lp); return; } + if (S_ISDIR(st.st_mode)) { + if (!recursive) { fprintf(stderr, "psftp: %s 是目录,需要 -r\n", lp); return; } + if (sftp_put_dir(p->sf, lp, rp) < 0) fprintf(stderr, "psftp: put 目录失败\n"); + } else { + if (sftp_put_file(p->sf, lp, rp) < 0) fprintf(stderr, "psftp: put 失败\n"); + } +} + +static void cmd_rm(psftp_t *p, const char *path) { + char rp[2048]; + if (path[0] == '/') snprintf(rp, sizeof(rp), "%s", path); + else rjoin(rp, sizeof(rp), p->rdir, path); + if (ssh_sftp_remove(p->sf, rp) < 0) + fprintf(stderr, "psftp: rm: %s 失败\n", rp); +} + +static void cmd_mkdir(psftp_t *p, const char *path) { + char rp[2048]; + if (path[0] == '/') snprintf(rp, sizeof(rp), "%s", path); + else rjoin(rp, sizeof(rp), p->rdir, path); + if (ssh_sftp_mkdir(p->sf, rp, 0755) < 0) + fprintf(stderr, "psftp: mkdir: %s 失败\n", rp); +} + +static void cmd_rmdir(psftp_t *p, const char *path) { + char rp[2048]; + if (path[0] == '/') snprintf(rp, sizeof(rp), "%s", path); + else rjoin(rp, sizeof(rp), p->rdir, path); + if (ssh_sftp_rmdir(p->sf, rp) < 0) + fprintf(stderr, "psftp: rmdir: %s 失败\n", rp); +} + +static void cmd_rename(psftp_t *p, const char *oldp, const char *newp) { + char o[2048], nw[2048]; + if (oldp[0] == '/') snprintf(o, sizeof(o), "%s", oldp); + else rjoin(o, sizeof(o), p->rdir, oldp); + if (newp[0] == '/') snprintf(nw, sizeof(nw), "%s", newp); + else rjoin(nw, sizeof(nw), p->rdir, newp); + if (ssh_sftp_rename(p->sf, o, nw) < 0) + fprintf(stderr, "psftp: rename: %s -> %s 失败\n", oldp, newp); +} + +static void help(void) { + printf("可用命令:\n" + " bye, exit, quit 退出 psftp\n" + " pwd / lpwd 显示远端 / 本地当前目录\n" + " cd / lcd 切换远端 / 本地目录\n" + " ls [path] 列出远端目录\n" + " lls [path] 列出本地目录\n" + " get [-r] [local] 下载(文件或目录)\n" + " put [-r] [remote] 上传(文件或目录)\n" + " rm 删除远端文件\n" + " mkdir 创建远端目录\n" + " rmdir 删除远端空目录\n" + " rename 远端重命名\n" + " help, ? 显示本帮助\n"); +} + +/* 执行单条命令;返回 1=退出 */ +static int run_line(psftp_t *p, char *line) { + /* 去首尾空白 */ + char *s = line; + while (*s == ' ' || *s == '\t') s++; + char *end = s + strlen(s); + while (end > s && (end[-1] == '\n' || end[-1] == '\r' || end[-1] == ' ' || + end[-1] == '\t')) end--; + *end = '\0'; + if (!*s) return 0; + + char *args[8]; + int nargs = 0; + char *tok = s; + while (tok && nargs < 8) { + while (*tok == ' ' || *tok == '\t') tok++; + if (!*tok) break; + args[nargs++] = tok; + char *sp = strchr(tok, ' '); + if (!sp) sp = strchr(tok, '\t'); + if (!sp) break; + *sp = '\0'; + tok = sp + 1; + } + + const char *c = args[0]; + if (strcmp(c, "bye") == 0 || strcmp(c, "exit") == 0 || strcmp(c, "quit") == 0) + return 1; + if (strcmp(c, "pwd") == 0) { cmd_pwd(p); return 0; } + if (strcmp(c, "lpwd") == 0) { cmd_lpwd(p); return 0; } + if (strcmp(c, "cd") == 0) { if (nargs > 1) cmd_cd(p, args[1]); return 0; } + if (strcmp(c, "lcd") == 0) { if (nargs > 1) cmd_lcd(p, args[1]); return 0; } + if (strcmp(c, "ls") == 0) { cmd_ls(p, nargs > 1 ? args[1] : NULL); return 0; } + if (strcmp(c, "lls") == 0) { cmd_lls(p, nargs > 1 ? args[1] : NULL); return 0; } + if (strcmp(c, "get") == 0) { + int rec = 0; + int i = 1; + if (nargs > 1 && strcmp(args[1], "-r") == 0) { rec = 1; i++; } + if (nargs > i) cmd_get(p, rec, args[i], nargs > i + 1 ? args[i + 1] : NULL); + return 0; + } + if (strcmp(c, "put") == 0) { + int rec = 0; + int i = 1; + if (nargs > 1 && strcmp(args[1], "-r") == 0) { rec = 1; i++; } + if (nargs > i) cmd_put(p, rec, args[i], nargs > i + 1 ? args[i + 1] : NULL); + return 0; + } + if (strcmp(c, "rm") == 0) { if (nargs > 1) cmd_rm(p, args[1]); return 0; } + if (strcmp(c, "mkdir") == 0) { if (nargs > 1) cmd_mkdir(p, args[1]); return 0; } + if (strcmp(c, "rmdir") == 0) { if (nargs > 1) cmd_rmdir(p, args[1]); return 0; } + if (strcmp(c, "rename") == 0) { if (nargs > 2) cmd_rename(p, args[1], args[2]); return 0; } + if (strcmp(c, "help") == 0 || strcmp(c, "?") == 0) { help(); return 0; } + fprintf(stderr, "psftp: 未知命令 \"%s\" (输入 help 查看)\n", c); + return 0; +} + +/* ============================================================ + * 入口 + * ============================================================ */ +static void usage(void) { + fprintf(stderr, + "psftp - SFTP 客户端 (与 OpenSSH sftp 兼容)\n" + "用法:\n" + " psftp [选项] [user@]host[:初始路径]\n" + "选项:\n" + " -P 远程端口 (默认 22)\n" + " -pwd 密码\n" + " -i 私钥文件 (publickey 认证)\n" + " -o StrictHostKeyChecking=no|accept-new|yes\n" + " -b 批处理模式:逐行执行命令后退出\n" + "示例:\n" + " psftp -P 6666 -pwd pass root@host\n" + " psftp root@host:/var/log -b batch.txt\n"); +} + +int main(int argc, char **argv) { +#ifdef _WIN32 + SetConsoleOutputCP(CP_UTF8); +#endif + cli_cfg_t cfg = { 22, NULL, NULL, NULL, 0 }; + const char *batch = NULL; + const char *target = NULL; + int i = 1; + for (; i < argc; i++) { + const char *a = argv[i]; + if (strcmp(a, "-P") == 0 && i + 1 < argc) { cfg.port = atoi(argv[++i]); } + else if (strncmp(a, "-P", 2) == 0 && strlen(a) > 2) { cfg.port = atoi(a + 2); } + else if (strcmp(a, "-pwd") == 0 && i + 1 < argc) { cfg.password_arg = argv[++i]; } + else if (strncmp(a, "-pwd=", 5) == 0) { cfg.password_arg = a + 5; } + else if (strcmp(a, "-i") == 0 && i + 1 < argc) { cfg.keyfile = argv[++i]; } + else if (strcmp(a, "-o") == 0 && i + 1 < argc) { + const char *kv = argv[++i]; + if (strncmp(kv, "StrictHostKeyChecking=", 22) == 0) { + const char *v = kv + 22; + if (strcmp(v, "no") == 0 || strcmp(v, "off") == 0) cfg.hostkey_mode = 2; + else if (strcmp(v, "accept-new") == 0) cfg.hostkey_mode = 1; + } + } else if (strcmp(a, "-b") == 0 && i + 1 < argc) { batch = argv[++i]; } + else if (a[0] == '-' && strlen(a) > 1) { continue; } + else { target = a; } + } + + if (!target) { usage(); return 1; } + + /* 拆分 [user@]host[:path] */ + char host[512], ruser[128], start_path[1024]; + const char *colon = strchr(target, ':'); + const char *at = strchr(target, '@'); + if (colon) { + size_t hl = (size_t)(colon - target); + if (at && at < colon) { + size_t ul = (size_t)(at - target); + if (ul < sizeof(ruser)) { memcpy(ruser, target, ul); ruser[ul] = '\0'; } + hl = (size_t)(colon - (at + 1)); + if (hl < sizeof(host)) { memcpy(host, at + 1, hl); host[hl] = '\0'; } + } else { + if (hl < sizeof(host)) { memcpy(host, target, hl); host[hl] = '\0'; } + } + snprintf(start_path, sizeof(start_path), "%s", colon + 1); + } else { + size_t tl = strlen(target); + size_t hl = tl; + if (at && (size_t)(at - target) < tl) { + size_t ul = (size_t)(at - target); + if (ul < sizeof(ruser)) { memcpy(ruser, target, ul); ruser[ul] = '\0'; } + hl = tl - ul - 1; + if (hl < sizeof(host)) { memcpy(host, at + 1, hl); host[hl] = '\0'; } + } else { + if (hl < sizeof(host)) { memcpy(host, target, hl); host[hl] = '\0'; } + } + start_path[0] = '\0'; + } + + ssh_session_t *s = connect_session(&cfg, host, ruser[0] ? ruser : NULL); + if (!s) return 1; + + uint32_t ch = 0; + if (ssh_channel_open(s, &ch, "session", 0, 0) < 0) { + fprintf(stderr, "psftp: 通道打开失败\n"); + ssh_session_free(s); + return 1; + } + ssh_sftp_t *sf = NULL; + if (ssh_sftp_start(&sf, s, ch) != 0) { + fprintf(stderr, "psftp: 远端不支持 SFTP 子系统\n"); + ssh_channel_close(s, ch); + ssh_session_free(s); + return 1; + } + + psftp_t p = { sf, ".", "" }; + cmd_lpwd(&p); + if (start_path[0]) cmd_cd(&p, start_path); + else cmd_pwd(&p); + + int rc = 0; + if (batch) { + FILE *bf = fopen(batch, "r"); + if (!bf) { fprintf(stderr, "psftp: 无法打开批处理文件 %s\n", batch); rc = 1; } + else { + char line[4096]; + int first = 1; + while (fgets(line, sizeof(line), bf)) { + if (first) { + first = 0; + /* 跳过 UTF-8 BOM(EF BB BF),否则首行命令会带上 BOM 无法识别 */ + uint8_t *lp = (uint8_t *)line; + if (lp[0] == 0xEF && lp[1] == 0xBB && lp[2] == 0xBF) + memmove(line, line + 3, strlen(line + 3) + 1); + } + if (run_line(&p, line)) break; + } + fclose(bf); + } + } else { + char line[4096]; + for (;;) { + fprintf(stderr, "psftp> "); + fflush(stderr); + if (!fgets(line, sizeof(line), stdin)) break; + if (run_line(&p, line)) break; + } + } + + ssh_sftp_free(sf); + ssh_channel_close(s, ch); + ssh_session_free(s); + return rc; +} diff --git a/apps/pssh/standalone.c b/apps/pssh/standalone.c new file mode 100644 index 0000000..b6b71d6 --- /dev/null +++ b/apps/pssh/standalone.c @@ -0,0 +1,65 @@ +/* standalone.c —— 独立命令入口 (pscp/pkeygen/pkeyscan/pcopy-id/pagent/padd/psshd) + * + * 根据可执行文件名决定命令名:去掉首字母 'p' (pscp → scp, pcopy-id → copy-id), + * 以 pssh 子命令风格调用对应实现 (pssh_cmd_*)。pssh.exe 内的子命令行为一致。 + */ +#include +#include + +#ifdef _WIN32 +#include +#endif + +int pssh_cmd_ssh(int argc, char **argv); +int pssh_cmd_keygen(int argc, char **argv); +int pssh_cmd_keyscan(int argc, char **argv); +int pssh_cmd_copyid(int argc, char **argv); +int pssh_cmd_agent(int argc, char **argv); +int pssh_cmd_add(int argc, char **argv); +int pssh_cmd_scp(int argc, char **argv); +int pssh_cmd_sshd(int argc, char **argv); + +static const char *exe_base(const char *p) { + const char *last = p; + for (const char *q = p; *q; q++) + if (*q == '/' || *q == '\\') last = q + 1; + return last; +} + +int main(int argc, char **argv) { +#ifdef _WIN32 + SetConsoleOutputCP(CP_UTF8); +#endif + const char *exe = exe_base(argv[0]); + size_t n = strlen(exe); + if (n > 4 && strcmp(exe + n - 4, ".exe") == 0) n -= 4; + if (n < 2 || n >= sizeof(char[64])) { + fprintf(stderr, "无法识别命令名\n"); + return 1; + } + /* pXxx → xxx */ + char cmd[64]; + if (exe[0] == 'p' || exe[0] == 'P') { + memcpy(cmd, exe + 1, n - 1); + cmd[n - 1] = '\0'; + } else { + memcpy(cmd, exe, n); + cmd[n] = '\0'; + } + + const char *av[64]; + int cnt = argc < 63 ? argc : 63; + for (int i = 0; i < cnt; i++) av[i] = argv[i]; + av[0] = cmd; + + if (strcmp(cmd, "scp") == 0) return pssh_cmd_scp(cnt, (char **)av); + if (strcmp(cmd, "keygen") == 0) return pssh_cmd_keygen(cnt, (char **)av); + if (strcmp(cmd, "keyscan") == 0) return pssh_cmd_keyscan(cnt, (char **)av); + if (strcmp(cmd, "copy-id") == 0) return pssh_cmd_copyid(cnt, (char **)av); + if (strcmp(cmd, "agent") == 0) return pssh_cmd_agent(cnt, (char **)av); + if (strcmp(cmd, "add") == 0) return pssh_cmd_add(cnt, (char **)av); + if (strcmp(cmd, "sshd") == 0) return pssh_cmd_sshd(cnt, (char **)av); + if (strcmp(cmd, "ssh") == 0) return pssh_cmd_ssh(cnt, (char **)av); + fprintf(stderr, "未知命令: p%s\n", cmd); + return 1; +} diff --git a/build.ps1 b/build.ps1 index e0cac6b..7a48af8 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,5 +1,6 @@ # build.ps1 —— 无 CMake 环境下的源码构建脚本 # 编译三个静态库(paze_crypto/paze_ssl/paze_ssh)与 CLI 应用(pssh/pazessl) +# 发布 exe 全部输出到 bin/ 文件夹,中间产物(obj/静态库)留在 build/ # 用法: .\build.ps1 [-Clean] [-Debug] param( [switch]$Clean, @@ -9,10 +10,11 @@ param( $ErrorActionPreference = "Continue" $Root = $PSScriptRoot $Build = Join-Path $Root "build" +$Bin = Join-Path $Root "bin" $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 +New-Item -ItemType Directory -Force -Path $Build, $Obj, $Bin | Out-Null $cc = "gcc" $opt = if ($Debug) { "-O0", "-g" } else { "-O2" } @@ -53,7 +55,7 @@ $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) + & $cc @cflags $Main @Libs -lws2_32 -ladvapi32 -o (Join-Path $Bin $Out) if ($LASTEXITCODE -ne 0) { throw "link app failed: $Out" } } @@ -63,7 +65,7 @@ $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" })) { +foreach ($ad in (Get-ChildItem (Join-Path $Root "apps") -Directory | Where-Object { $_.Name -ne "pazessl" -and $_.Name -ne "pssh" -and $_.Name -ne "psftp" })) { $am = Join-Path $ad.FullName "main.c" if (-not (Test-Path $am)) { continue } $o = Join-Path $Obj ("app_" + $ad.Name + ".o") @@ -73,9 +75,26 @@ foreach ($ad in (Get-ChildItem (Join-Path $Root "apps") -Directory | Where-Objec } $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") +& $cc @cflags $appObjs @($sshA, $cryptoA) -lws2_32 -ladvapi32 -o (Join-Path $Bin "pssh.exe") if ($LASTEXITCODE -ne 0) { throw "link pssh failed" } +# 独立命令: pscp/pkeygen/pkeyscan/pcopy-id/pagent/padd/psshd +# (standalone.c 按 exe 名分发到对应 pssh_cmd_*;链接各 app 实现,但排除 pssh main) +Write-Host "== standalone ==" +$standaloneObj = Join-Path $Obj "app_standalone.o" +& $cc @cflags -c (Join-Path $Root "apps\pssh\standalone.c") -o $standaloneObj +if ($LASTEXITCODE -ne 0) { throw "compile standalone failed" } +$standaloneLibs = ($appObjs | Where-Object { $_ -notlike "*app_pssh_main*" }) + @($sshA, $cryptoA) +foreach ($scmd in @("pscp", "pkeygen", "pkeyscan", "pcopy-id", "pagent", "padd", "psshd")) { + & $cc @cflags $standaloneObj $standaloneLibs -lws2_32 -ladvapi32 -o (Join-Path $Bin "$scmd.exe") + if ($LASTEXITCODE -ne 0) { throw "link $scmd failed" } +} + +# psftp: 独立 SFTP 客户端(自有 main,不并入 pssh) +if (Test-Path (Join-Path $Root "apps\psftp\main.c")) { + Link-App (Join-Path $Root "apps\psftp\main.c") @($sshA, $cryptoA) "psftp.exe" +} + Link-App (Join-Path $Root "apps\pazessl\main.c") @($sslA, $cryptoA) "pazessl.exe" Write-Host "== done ==" diff --git a/build/run_tls_test.ps1 b/build/run_tls_test.ps1 index 8df75e3..e0f376a 100644 --- a/build/run_tls_test.ps1 +++ b/build/run_tls_test.ps1 @@ -1,7 +1,7 @@ # TLS 1.2/1.3 自测脚本(一次性) $ErrorActionPreference = "Continue" $Root = "F:\TLS-SSH" -$exe = Join-Path $Root "build\pazessl.exe" +$exe = Join-Path $Root "bin\pazessl.exe" $key = Join-Path $Root "build\key.pem" $cert = Join-Path $Root "build\cert.pem" @@ -57,3 +57,38 @@ Test-Conn "fallback-cli" "14435" @("s_server","-accept","14435","-cert",$cert,"- # 回退场景 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") + +# TLS 1.3 PSK 会话恢复:同一服务端进程内两连接,第二连接用第一连接的 session +function Test-PskResume { + $port = 14437 + $sess = Join-Path $Root "build\session_resume.bin" + Remove-Item -ErrorAction SilentlyContinue $sess + $serr = Join-Path $Root "build\srv_psk.err.log" + $sout = Join-Path $Root "build\srv_psk.out.log" + Remove-Item -ErrorAction SilentlyContinue $serr, $sout + $srv = Start-Process -FilePath $exe -ArgumentList @("s_server","-accept","$port","-cert",$cert,"-key",$key,"-reuse","2") ` + -NoNewWindow -PassThru -RedirectStandardOutput $sout -RedirectStandardError $serr + Start-Sleep -Milliseconds 1200 + # 连接 #1: 全握手,保存 session + & $exe s_client -connect "127.0.0.1:$port" -msg hi -sess_out $sess 2>&1 | Out-Null + Start-Sleep -Milliseconds 500 + # 连接 #2: 用 session 恢复 + $c2out = Join-Path $Root "build\cli_psk2.err.log" + Remove-Item -ErrorAction SilentlyContinue $c2out + $c2 = Start-Process -FilePath $exe -ArgumentList @("s_client","-connect","127.0.0.1:$port","-msg","hi","-sess_in",$sess) ` + -NoNewWindow -PassThru -RedirectStandardError $c2out + $resumed = $false + for ($i = 0; $i -lt 20; $i++) { + Start-Sleep -Milliseconds 300 + $t = Get-Content $c2out -Raw -ErrorAction SilentlyContinue + if ($t -match "resumed YES") { $resumed = $true; break } + if ($c2.HasExited) { break } + } + if (-not $c2.HasExited) { Stop-Process -Id $c2.Id -Force } + if (-not $srv.HasExited) { Stop-Process -Id $srv.Id -Force } + Write-Host "== psk-resume ==" + Write-Host "session-file=$([bool](Test-Path $sess)) resumed=$resumed" + if (Test-Path $serr) { Get-Content $serr | ForEach-Object { Write-Host "S: $_" } } + if (Test-Path $c2out) { Get-Content $c2out | ForEach-Object { Write-Host "C2: $_" } } +} +Test-PskResume diff --git a/down.bin b/down.bin new file mode 100644 index 0000000..9b588f7 --- /dev/null +++ b/down.bin @@ -0,0 +1,4 @@ +hello-psftp + + + diff --git a/include/paze/tls.h b/include/paze/tls.h index 2b59ead..dabba7a 100644 --- a/include/paze/tls.h +++ b/include/paze/tls.h @@ -171,6 +171,24 @@ typedef enum { PAZE_TLS_VERIFY_REQUIRED = 2 /* 默认:服务端必须可信 */ } paze_tls_verify_mode_t; +/* ============================================================ + * 会话复用(TLS 1.3 PSK 会话恢复) + * ============================================================ */ +typedef struct { + uint8_t ticket[512]; /* NewSessionTicket 的 ticket 字段 */ + size_t ticket_len; + uint8_t psk[64]; /* 由 resumption_master_secret 派生的 PSK */ + size_t psk_len; + uint32_t age_add; /* ticket_age_add */ + int32_t recv_time; /* 收到 ticket 的 unix 时间(用于 obfuscated age) */ + int hash; /* 关联 hash: 0=SHA-256, 1=SHA-384 */ + int valid; +} paze_tls_session_t; + +/* 客户端:设置要用于会话恢复的 PSK 会话(来自上一次 paze_tls_get_session) */ +paze_status_t paze_tls_config_set_session(paze_tls_config_t *cfg, + const paze_tls_session_t *sess); + paze_tls_config_t *paze_tls_config_new(paze_tls_role_t role); void paze_tls_config_free(paze_tls_config_t *cfg); @@ -203,6 +221,19 @@ paze_status_t paze_tls_config_set_versions(paze_tls_config_t *cfg, * ============================================================ */ typedef struct paze_tls paze_tls_t; +/* 握手后获取服务端下发的会话(客户端收到 NewSessionTicket 后); + * 未收到 ticket 或非 1.3 时返回 valid=0。 */ +paze_status_t paze_tls_get_session(const paze_tls_t *tls, paze_tls_session_t *sess); +/* 本次握手是否通过 PSK 会话恢复(客户端/服务端均适用) */ +int paze_tls_session_resumed(const paze_tls_t *tls); +/* 服务端:导出本次连接使用的 session ticket key(供多连接共享以实现恢复); + * ready 返回 0 表示尚未生成。 */ +paze_status_t paze_tls_get_session_ticket_key(const paze_tls_t *tls, + uint8_t key[32], int *ready); +/* 服务端:显式设置 session ticket key(使所有连接用同一密钥) */ +paze_status_t paze_tls_config_set_session_ticket_key(paze_tls_config_t *cfg, + const uint8_t key[32]); + paze_tls_t *paze_tls_new(const paze_tls_config_t *cfg); void paze_tls_free(paze_tls_t *tls); diff --git a/src/crypto/x509.c b/src/crypto/x509.c index ab5daa7..bc17e87 100644 --- a/src/crypto/x509.c +++ b/src/crypto/x509.c @@ -472,7 +472,7 @@ static int pubkey_eq(const paze_x509_cert_t *a, const paze_x509_cert_t *b) { uint8_t ab[65], bb[65]; if (paze_p256_to_affine(&a->ecdsa_pub.Q, ab, 65) != PAZE_OK) return 0; if (paze_p256_to_affine(&b->ecdsa_pub.Q, bb, 65) != PAZE_OK) return 0; - return paze_ct_equal(ab, bb, 65); + return paze_ct_equal(ab, bb, 65) == 0; /* ct_equal:0=相等 */ } return 0; } diff --git a/src/ssh/sftp_server.c b/src/ssh/sftp_server.c index 11a7b7d..0857431 100644 --- a/src/ssh/sftp_server.c +++ b/src/ssh/sftp_server.c @@ -66,6 +66,7 @@ static int srv_open_file(const char *path, uint32_t pflags) { if (pflags & SSH_FXF_TRUNC) of |= O_TRUNC; if (pflags & SSH_FXF_EXCL) of |= O_EXCL; #ifdef _WIN32 + of |= O_BINARY; /* 二进制模式:否则 _write 会把 \n 转成 \r\n,损坏上传内容 */ wchar_t w[2048]; if (utf8_to_wide(path, w, 2048) < 0) return -1; return _wopen(w, of, _S_IREAD | _S_IWRITE); diff --git a/src/ssl/tls_conn.c b/src/ssl/tls_conn.c index 771cb11..3e33145 100644 --- a/src/ssl/tls_conn.c +++ b/src/ssl/tls_conn.c @@ -213,6 +213,63 @@ paze_status_t paze_tls_config_set_versions(paze_tls_config_t *cfg, return PAZE_OK; } +/* ============================================================ + * TLS 1.3 PSK 会话恢复公开 API + * ============================================================ */ +paze_status_t paze_tls_config_set_session(paze_tls_config_t *cfg, + const paze_tls_session_t *sess) { + if (!cfg || !sess) return PAZE_ERR_INVALID; + if (sess->valid && (sess->ticket_len == 0 || sess->ticket_len > sizeof(sess->ticket) || + sess->psk_len == 0 || sess->psk_len > sizeof(sess->psk))) + return PAZE_ERR_INVALID; + cfg->session = *sess; /* 值拷贝 */ + return PAZE_OK; +} + +/* 握手后获取服务端下发的会话(客户端在收到 NewSessionTicket 后有效)。 + * 未收到 ticket 或非 1.3 时返回 valid=0。 */ +paze_status_t paze_tls_get_session(const paze_tls_t *tls, paze_tls_session_t *sess) { + if (!tls || !sess) return PAZE_ERR_INVALID; + memset(sess, 0, sizeof(*sess)); + if (tls->neg.version != PAZE_TLS_VERSION_1_3 || !tls->have_ticket || + tls->recv_ticket_len == 0 || tls->psk_len == 0) + return PAZE_OK; + if (tls->recv_ticket_len > sizeof(sess->ticket) || tls->psk_len > sizeof(sess->psk)) + return PAZE_OK; + memcpy(sess->ticket, tls->recv_ticket, tls->recv_ticket_len); + sess->ticket_len = tls->recv_ticket_len; + memcpy(sess->psk, tls->psk, tls->psk_len); + sess->psk_len = tls->psk_len; + sess->age_add = tls->ticket_age_add; + sess->recv_time = tls->ticket_recv_time; + sess->hash = tls->psk_hash; + sess->valid = 1; + return PAZE_OK; +} + +/* 本次握手是否通过 PSK 会话恢复(客户端/服务端均适用) */ +int paze_tls_session_resumed(const paze_tls_t *tls) { + return tls ? (tls->psk_mode && tls->handshake_done) : 0; +} + +/* 服务端:导出 session ticket key(位于 cfg,cfg_owned 在 paze_tls_new 时拷贝) */ +paze_status_t paze_tls_get_session_ticket_key(const paze_tls_t *tls, + uint8_t key[32], int *ready) { + if (!tls || !key || !ready) return PAZE_ERR_INVALID; + paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg; + *ready = cfg->ticket_key_ready; + if (cfg->ticket_key_ready) memcpy(key, cfg->ticket_key, 32); + return PAZE_OK; +} + +paze_status_t paze_tls_config_set_session_ticket_key(paze_tls_config_t *cfg, + const uint8_t key[32]) { + if (!cfg || !key) return PAZE_ERR_INVALID; + memcpy(cfg->ticket_key, key, 32); + cfg->ticket_key_ready = 1; + return PAZE_OK; +} + /* ============================================================ * 连接对象 * ============================================================ */ diff --git a/src/ssl/tls_extensions.c b/src/ssl/tls_extensions.c index ea12579..ac13c10 100644 --- a/src/ssl/tls_extensions.c +++ b/src/ssl/tls_extensions.c @@ -279,6 +279,109 @@ size_t paze_tls_ext_write_renegotiation_info(uint8_t *buf) { return put_ext_header(buf, PAZE_TLS_EXT_RENEGOTIATION_INFO, body) + body; } +/* ============================================================ + * PSK 会话恢复扩展 (RFC 8446 §4.2.11) + * ============================================================ */ +/* psk_key_exchange_modes(客户端): modes = psk_ke(0) | psk_dhe_ke(1) + * body = [1][0x00][0x01] */ +size_t paze_tls_ext_write_psk_key_exchange_modes(uint8_t *buf) { + uint8_t *p = buf + 4; + p[0] = 0x02; p[1] = 0x00; p[2] = 0x01; /* len=2, psk_ke, psk_dhe_ke */ + size_t body = 3; + return put_ext_header(buf, PAZE_TLS_EXT_PSK_KEY_EXCHANGE_MODES, body) + body; +} + +/* pre_shared_key(客户端): + * body = identities: 2 字节总长 || PskIdentity ×N + * binders: 2 字节总长 || PskBinderEntry ×N + * PskIdentity = identity(2 字节长 + bytes) || obfuscated_ticket_age(4) */ +size_t paze_tls_ext_write_pre_shared_key(uint8_t *buf, + const uint8_t *identity, size_t id_len, + uint32_t obfuscated_age, + const uint8_t *binder, size_t binder_len) { + uint8_t *p = buf + 4; + uint8_t *ident_len_p = p; p += 2; + paze_store16_be(p, (uint16_t)id_len); p += 2; + memcpy(p, identity, id_len); p += id_len; + paze_store32_be(p, obfuscated_age); p += 4; + size_t ident_total = (size_t)(p - ident_len_p); + paze_store16_be(ident_len_p, (uint16_t)(ident_total - 2)); + + uint8_t *binder_len_p = p; p += 2; + paze_store16_be(p, (uint16_t)binder_len); p += 2; + if (binder_len) { + memcpy(p, binder, binder_len); p += binder_len; + } + size_t binder_total = (size_t)(p - binder_len_p); + paze_store16_be(binder_len_p, (uint16_t)(binder_total - 2)); + + size_t body = (size_t)(p - (buf + 4)); + return put_ext_header(buf, PAZE_TLS_EXT_PRE_SHARED_KEY, body) + body; +} + +/* ServerHello 的 pre_shared_key: selected_identity(2 字节) */ +size_t paze_tls_ext_write_psk_selected(uint8_t *buf, uint16_t selected) { + uint8_t *p = buf + 4; + paze_store16_be(p, selected); + size_t body = 2; + return put_ext_header(buf, PAZE_TLS_EXT_PRE_SHARED_KEY, body) + body; +} + +/* 解析 pre_shared_key:取第一个 identity 与 binder + * body = identities: len(2) || (identity_len(2)+identity || age(4))×N + * binders: len(2) || (binder_len(1)+binder)×N */ +int paze_tls_ext_parse_pre_shared_key(const uint8_t *data, size_t len, + const uint8_t **id, size_t *idlen, + uint32_t *obfuscated_age, + const uint8_t **binder, size_t *binderlen) { + if (len < 4) return 0; + size_t il = paze_load16_be(data); + if (len < 2 + il + 2) return 0; + size_t off = 2; + if (il >= 6) { + size_t idl = paze_load16_be(data + off); + if (2 + idl + 4 <= il) { + *id = data + off + 2; *idlen = idl; + *obfuscated_age = paze_load32_be(data + off + 2 + idl); + } else { + return 0; + } + } + off += il; + if (off + 2 > len) return 0; + size_t bl = paze_load16_be(data + off); + if (off + 2 + bl > len) return 0; + off += 2; + if (bl >= 1) { + size_t bdl = data[off]; + if (1 + bdl <= bl && bdl > 0) { + *binder = data + off + 1; *binderlen = bdl; + } else { + *binder = NULL; *binderlen = 0; + } + } else { + *binder = NULL; *binderlen = 0; + } + return 1; +} + +/* 解析 ServerHello pre_shared_key */ +int paze_tls_ext_parse_psk_selected(const uint8_t *data, size_t len, + uint16_t *selected) { + if (len != 2) return 0; + *selected = paze_load16_be(data); + return 1; +} + +/* psk_key_exchange_modes: body = len(1) || modes */ +int paze_tls_ext_parse_psk_ke_modes(const uint8_t *data, size_t len) { + if (len < 1) return 0; + size_t ml = data[0]; + if (len != 1 + ml) return 0; + for (size_t i = 0; i < ml; i++) if (data[1 + i] == 0x00) return 1; /* psk_ke */ + return 0; +} + /* ============================================================ * 通用扩展遍历器: * 对 extensions 块(含外层 2 字节 ext_total_len)逐条调用 cb。 diff --git a/src/ssl/tls_handshake13.c b/src/ssl/tls_handshake13.c index 333ac87..ba8c1c1 100644 --- a/src/ssl/tls_handshake13.c +++ b/src/ssl/tls_handshake13.c @@ -1,8 +1,10 @@ /* tls_handshake13.c —— TLS 1.3 (RFC 8446) 完整握手状态机 * - * 覆盖的握手流程(无 PSK, 无 0-RTT): + * 覆盖的握手流程(无 0-RTT): * Client: ClientHello → 收 ServerHello → 收 EncryptedExtensions → 收 Certificate/CertificateVerify/Finished → 发 Finished * Server: 收 ClientHello → 发 ServerHello → 发 EncryptedExtensions/Certificate/CertificateVerify/Finished → 收 Finished + * PSK 会话恢复:客户端 CH 携带 pre_shared_key,服务端验证 ticket+binder 后 + * SH 回 selected_identity,握手跳过证书;服务端握手结束下发 NewSessionTicket。 * * 所有 C++ lambda 已转为静态 C 回调函数。 */ @@ -125,6 +127,7 @@ static paze_status_t recv_hs(paze_tls_t *tls, * ============================================================ */ 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; + paze_status_t st = PAZE_OK; uint8_t *p = out; p += 4; @@ -165,6 +168,57 @@ paze_status_t paze_tls13_build_ch(paze_tls_t *tls, uint8_t *out, size_t *outlen) n = paze_tls_ext_write_alpn(p, cfg->alpn, cfg->alpn_count); p += n; n = paze_tls_ext_write_renegotiation_info(p); p += n; + /* PSK 会话恢复:psk_key_exchange_modes + pre_shared_key(必须最后)。 + * pre_shared_key 分两段构造:binder 数据不参与 binder 的 transcript hash, + * 因此先写 identities + binders 总长度,计算 hash,再填 binder。 */ + if (tls->offered_psk && cfg->session.valid) { + PAZE_LOGD("CH offering PSK (hash=%d psklen=%zu ticketlen=%zu)", + tls->psk_hash, cfg->session.psk_len, cfg->session.ticket_len); + const paze_tls_session_t *ss = &cfg->session; + n = paze_tls_ext_write_psk_key_exchange_modes(p); p += n; + + uint32_t age_ms = (uint32_t)((int32_t)time(NULL) - ss->recv_time) * 1000; + uint32_t obf = ss->age_add + age_ms; + + uint8_t *psk_hdr = p; p += 4; /* pre_shared_key 扩展头占位 */ + uint8_t *ident_len_p = p; p += 2; /* identities 总长 */ + paze_store16_be(p, (uint16_t)cfg->session.ticket_len); p += 2; + memcpy(p, cfg->session.ticket, cfg->session.ticket_len); p += cfg->session.ticket_len; + paze_store32_be(p, obf); p += 4; + size_t il = (size_t)(p - ident_len_p); + paze_store16_be(ident_len_p, (uint16_t)(il - 2)); + + uint8_t *binders_total_p = p; p += 2; /* binders 总长(先占位) */ + size_t hl = (tls->psk_hash == 1) ? 48 : 32; + paze_store16_be(binders_total_p, (uint16_t)(1 + hl)); + + /* binder 的 transcript hash 覆盖到 binders 长度字段为止,其中包含 + * 握手头、ext_total 与 pre_shared_key 扩展长度字段;binder 长度为 + * 已知量(1+hl),故计算 binder 前必须先回填这些长度,使哈希输入 + * 与最终发送的 ClientHello 完全一致。 */ + paze_tls_hs_put_header(out, PAZE_TLS_HS_CLIENT_HELLO, + (size_t)(p - (out + 4)) + 1 + hl); + paze_store16_be(ext_total_len_ptr, + (uint16_t)((size_t)(p - (ext_total_len_ptr + 2)) + 1 + hl)); + paze_store16_be(psk_hdr + 0, PAZE_TLS_EXT_PRE_SHARED_KEY); + paze_store16_be(psk_hdr + 2, + (uint16_t)((size_t)(p - (psk_hdr + 4)) + 1 + hl)); + + size_t ch_partial = (size_t)(p - out); /* binder 计算点 */ + uint8_t tr_h[64]; + if (tls->psk_hash == 1) paze_sha384(out, ch_partial, tr_h); + else paze_sha256(out, ch_partial, tr_h); + uint8_t binder[64]; + st = paze_tls13_compute_binder(tls, tr_h, hl, binder); + if (st != PAZE_OK) return st; + *p++ = (uint8_t)hl; + memcpy(p, binder, hl); p += hl; + + size_t psk_body = (size_t)(p - (psk_hdr + 4)); + paze_store16_be(psk_hdr + 0, PAZE_TLS_EXT_PRE_SHARED_KEY); + paze_store16_be(psk_hdr + 2, (uint16_t)psk_body); + } + size_t ext_total = (size_t)(p - (ext_total_len_ptr + 2)); paze_store16_be(ext_total_len_ptr, (uint16_t)ext_total); @@ -284,6 +338,8 @@ typedef struct { uint16_t *g; const uint8_t **pub; size_t *plen; + uint16_t *sel_psk; /* ServerHello pre_shared_key selected_identity */ + int *fpsk; /* ServerHello 含 pre_shared_key */ } sh_ext_ctx_t; static int sh_ext_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { @@ -294,6 +350,8 @@ static int sh_ext_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { } else if (tp == PAZE_TLS_EXT_KEY_SHARE) { *c->g = paze_tls_ext_parse_key_share_server(b, l, c->pub, c->plen); if (*c->g) *c->fks = 1; + } else if (tp == PAZE_TLS_EXT_PRE_SHARED_KEY) { + if (paze_tls_ext_parse_psk_selected(b, l, c->sel_psk)) *c->fpsk = 1; } return 0; } @@ -347,6 +405,43 @@ static int ks_lookup_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) { /* ============================================================ * [D] 客户端握手 * ============================================================ */ + +/* 握手完成后(post-handshake)接收 NewSessionTicket(RFC 8446 §4.6.1): + * 解析 ticket 并派生恢复用 PSK,填充 tls->recv_ticket / tls->psk 等。 + * 服务端在握手结束前已发送,此处读取数据已在缓冲,不会长时间阻塞。 */ +static void client_recv_new_session_ticket(paze_tls_t *tls) { + /* resumption_master_secret 用完整握手 transcript(CH...client Finished); + * 必须先于 recv_hs 派生,因为 recv_hs 会把 ticket 计入 transcript。 */ + if (paze_tls13_derive_resumption_master(tls) != PAZE_OK) return; + + const uint8_t *body; size_t blen; uint8_t type; + if (recv_hs(tls, &body, &blen, &type, NULL, NULL) != PAZE_OK) return; + if (type != PAZE_TLS_HS_NEW_SESSION_TICKET) return; + if (blen < 4 + 4 + 1 + 2) return; + size_t p = 0; + tls->ticket_lifetime = paze_load32_be(body + p); p += 4; + tls->ticket_age_add = paze_load32_be(body + p); p += 4; + size_t nlen = body[p]; p += 1; + if (blen < p + nlen + 2) return; + if (nlen > sizeof(tls->ticket_nonce)) nlen = sizeof(tls->ticket_nonce); + memcpy(tls->ticket_nonce, body + p, nlen); + tls->ticket_nonce_len = nlen; p += nlen; + size_t tlen = paze_load16_be(body + p); p += 2; + if (blen != p + tlen) return; + if (tlen == 0 || tlen > sizeof(tls->recv_ticket)) return; + memcpy(tls->recv_ticket, body + p, tlen); + tls->recv_ticket_len = tlen; + tls->ticket_recv_time = (int32_t)time(NULL); + tls->have_ticket = 1; + + /* PSK = HKDF-Expand-Label(resumption_master_secret, "resumption", nonce, Hash.length) */ + tls->psk_hash = (tls->neg.hash == paze_hash_sha384()) ? 1 : 0; + size_t plen = sizeof(tls->psk); + if (paze_tls13_derive_resumption_psk(tls, tls->ticket_nonce, tls->ticket_nonce_len, + tls->psk, &plen) == PAZE_OK) + tls->psk_len = plen; +} + paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg; paze_status_t st; @@ -356,6 +451,19 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { if (st != PAZE_OK) return st; paze_tls_tr_init(&tls->transcript, paze_hash_sha256()); + /* PSK 会话恢复准备:若配置了可用会话,设置 offered_psk 并派生 + * binder_key(供 ClientHello 计算 binder)。binder 的 hash 用 PSK + * 关联 hash,与最终协商的 cipher 无关。 */ + if (cfg->session.valid && cfg->session.psk_len > 0 && cfg->session.ticket_len > 0) { + tls->offered_psk = 1; + tls->psk_hash = cfg->session.hash; + tls->psk_len = cfg->session.psk_len; + memcpy(tls->psk, cfg->session.psk, cfg->session.psk_len); + st = paze_tls13_psk_early_secret(tls, cfg->session.psk, + cfg->session.psk_len, cfg->session.hash); + if (st != PAZE_OK) return st; + } + uint8_t ch[PAZE_TLS_MAX_RECORD]; size_t ch_len = 0; st = paze_tls13_build_ch(tls, ch, &ch_len); if (st != PAZE_OK) return st; @@ -425,14 +533,31 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { int found_v = 0, found_ks = 0; uint16_t group = 0; const uint8_t *ks_pub = NULL; size_t ks_publen = 0; - sh_ext_ctx_t sx = { &found_v, &found_ks, &group, &ks_pub, &ks_publen }; + uint16_t sel_psk = 0; int fpsk = 0; + sh_ext_ctx_t sx = { &found_v, &found_ks, &group, &ks_pub, &ks_publen, + &sel_psk, &fpsk }; paze_tls_extensions_foreach(exts, 2 + ext_total, sh_ext_cb, &sx); - if (!found_v || !found_ks) { paze_tls_set_err(tls, "SH missing required ext"); return PAZE_ERR_PROTOCOL; } + if (!found_v) { paze_tls_set_err(tls, "SH missing required ext"); return PAZE_ERR_PROTOCOL; } + /* 服务端接受 PSK:selected_identity=0(客户端只提供 1 个 identity) */ + if (fpsk && sel_psk == 0 && tls->offered_psk) { + tls->psk_mode = 1; + } + if (!found_ks) { + /* psk_ke 模式:服务端不返回 key_share(仅当接受 PSK 且无需 ECDHE) */ + if (!tls->psk_mode) { + paze_tls_set_err(tls, "SH missing key share"); + return PAZE_ERR_PROTOCOL; + } + } tls->neg.group = group; uint8_t shared[64]; size_t shared_len = 0; - st = paze_tls_kex_ecdh(&tls->kex, group, ks_pub, ks_publen, shared, &shared_len); - if (st != PAZE_OK) return st; + if (found_ks) { + st = paze_tls_kex_ecdh(&tls->kex, group, ks_pub, ks_publen, shared, &shared_len); + if (st != PAZE_OK) return st; + } else { + shared_len = 0; /* psk_ke: 无 ECDHE,handshake_secret IKM 用 0 */ + } st = paze_tls13_derive_handshake_keys(tls, shared, shared_len); paze_memzero(shared, shared_len); @@ -568,6 +693,9 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { tls->write_key = &tls->app_write; tls->handshake_done = 1; tls->hs_state = PAZE_HS_CL_DONE; + + /* 读取服务端的 NewSessionTicket(post-handshake,用 app keys 加密) */ + client_recv_new_session_ticket(tls); } return PAZE_OK; } @@ -575,6 +703,154 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) { /* ============================================================ * [E] 服务端握手 * ============================================================ */ + +/* ticket 有效期(秒)与内部格式: + * [ver(1)][hash(1)][psk_len(1)][psk][lifetime(4)][mac(16)] + * mac = HMAC-SHA256(ticket_key, ticket[0:len-16]) 截断 16。 */ +#define PAZE_TLS_TICKET_LIFETIME (7 * 24 * 3600) +#define PAZE_TLS_TICKET_MAC_LEN 16 +#define PAZE_TLS_TICKET_VER 1 + +/* 服务端:解析 CH 的 pre_shared_key,验证 ticket 认证码与 PSK binder。 + * 成功返回 1,并设置 tls->psk / psk_len / psk_hash(由调用方置 psk_mode)。 + * exts 为 CH 扩展块(含 2 字节总长);ch_raw 为完整 ClientHello(含 4 字节头)。 */ +static int server_try_psk(paze_tls_t *tls, const uint8_t *exts, size_t exts_len, + const uint8_t *ch_raw, size_t ch_raw_len) { + /* 定位 pre_shared_key 扩展(客户端保证其为最后一个扩展) */ + const uint8_t *psk_body = NULL; size_t psk_body_len = 0; + size_t total = paze_load16_be(exts); + if (exts_len != 2 + total) { PAZE_LOGD("psk: ext total mismatch"); return 0; } + size_t off = 2; + while (off + 4 <= 2 + total) { + uint16_t tp = paze_load16_be(exts + off); + size_t el = paze_load16_be(exts + off + 2); + if (off + 4 + el > 2 + total) return 0; + if (tp == PAZE_TLS_EXT_PRE_SHARED_KEY) { + psk_body = exts + off + 4; + psk_body_len = el; + break; + } + off += 4 + el; + } + if (!psk_body) { PAZE_LOGD("psk: no pre_shared_key ext"); return 0; } + + const uint8_t *id = NULL, *binder = NULL; + size_t idlen = 0, binderlen = 0; + uint32_t obf = 0; + if (!paze_tls_ext_parse_pre_shared_key(psk_body, psk_body_len, &id, &idlen, + &obf, &binder, &binderlen)) { + PAZE_LOGD("psk: parse psk body failed (len=%zu)", psk_body_len); + return 0; + } + + /* 校验 ticket 内部格式与 MAC */ + paze_tls_config_t *cfgc = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg; + if (idlen < 3 + 1 + 4 + PAZE_TLS_TICKET_MAC_LEN) { PAZE_LOGD("psk: id too short %zu", idlen); return 0; } + if (id[0] != PAZE_TLS_TICKET_VER) { PAZE_LOGD("psk: bad ver %d", id[0]); return 0; } + size_t pl = id[2]; + if (idlen != 3 + pl + 4 + PAZE_TLS_TICKET_MAC_LEN) { PAZE_LOGD("psk: id len mismatch %zu vs %zu", idlen, 3 + pl + 4 + PAZE_TLS_TICKET_MAC_LEN); return 0; } + if (pl == 0 || pl > sizeof(tls->psk)) { PAZE_LOGD("psk: bad pl %zu", pl); return 0; } + if (!cfgc->ticket_key_ready) { PAZE_LOGD("psk: ticket_key not ready"); return 0; } + uint8_t mac[32]; + paze_hmac(paze_hash_sha256(), cfgc->ticket_key, 32, id, 3 + pl + 4, mac); + if (paze_ct_equal(mac, id + 3 + pl + 4, PAZE_TLS_TICKET_MAC_LEN) != 0) { + PAZE_LOGD("psk: ticket MAC mismatch"); + return 0; + } + uint32_t lifetime = paze_load32_be(id + 3 + pl); + if (lifetime == 0 || (uint32_t)(int32_t)time(NULL) - lifetime >= PAZE_TLS_TICKET_LIFETIME) { + PAZE_LOGD("psk: ticket expired (lifetime=%u now=%d)", lifetime, (int32_t)time(NULL)); + return 0; + } + + /* 提取 PSK 与关联 hash */ + tls->psk_hash = id[1]; + if (tls->psk_hash != 0 && tls->psk_hash != 1) { PAZE_LOGD("psk: bad hash %d", tls->psk_hash); return 0; } + tls->psk_len = pl; + memcpy(tls->psk, id + 3, pl); + + /* 验证 binder: + * binder = HMAC(binder_key, Hash(CH 去掉 binders 列表)) + * 客户端在 CH 构造时对"到 binders 长度字段为止"的部分做 hash; + * binders 列表长度 = bl(在 pre_shared_key body 内 binders 总长字段)。 */ + const paze_hash_alg_t *H = (tls->psk_hash == 1) ? paze_hash_sha384() : paze_hash_sha256(); + if (binderlen != H->digest_len) { PAZE_LOGD("psk: binder len %zu != %zu", binderlen, H->digest_len); return 0; } + size_t il = paze_load16_be(psk_body); + if (psk_body_len < 2 + il + 2) { PAZE_LOGD("psk: body too short"); return 0; } + size_t bl = paze_load16_be(psk_body + 2 + il); + if (ch_raw_len < bl) { PAZE_LOGD("psk: ch too short %zu < %zu", ch_raw_len, bl); return 0; } + size_t partial_len = ch_raw_len - bl; /* CH 去掉 binder 列表(保留 binders 长度字段) */ + uint8_t ph[64]; + if (tls->psk_hash == 1) paze_sha384(ch_raw, partial_len, ph); + else paze_sha256(ch_raw, partial_len, ph); + + if (paze_tls13_psk_early_secret(tls, tls->psk, tls->psk_len, tls->psk_hash) != PAZE_OK) { + PAZE_LOGD("psk: early secret failed"); + return 0; + } + uint8_t exp_binder[64]; + if (paze_tls13_compute_binder(tls, ph, H->digest_len, exp_binder) != PAZE_OK) { + PAZE_LOGD("psk: compute binder failed"); + return 0; + } + if (paze_ct_equal(exp_binder, binder, binderlen) != 0) { + PAZE_LOGD("psk: binder mismatch"); + return 0; + } + PAZE_LOGD("psk: PSK accepted (hash=%d psklen=%zu)", tls->psk_hash, tls->psk_len); + return 1; +} + +/* 服务端:握手完成后下发 NewSessionTicket(RFC 8446 §4.6.1)。 + * 需先派生 resumption_master_secret;消息用 app traffic keys 加密。 */ +static paze_status_t send_new_session_ticket(paze_tls_t *tls) { + uint8_t nonce[8]; + if (paze_random(nonce, sizeof(nonce)) != PAZE_OK) return PAZE_ERR_PLATFORM; + uint8_t psk[64]; size_t psk_len = sizeof(psk); + paze_status_t st = paze_tls13_derive_resumption_psk(tls, nonce, sizeof(nonce), + psk, &psk_len); + if (st != PAZE_OK) return st; + + /* 构造 ticket(认证封装)。ticket_key 存于 cfg:同一 cfg 的多连接共享, + * 实现跨连接会话恢复。 */ + paze_tls_config_t *cfgc = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg; + if (!cfgc->ticket_key_ready) { + if (paze_random(cfgc->ticket_key, 32) != PAZE_OK) return PAZE_ERR_PLATFORM; + cfgc->ticket_key_ready = 1; + } + uint8_t body[512]; size_t bp = 0; + body[bp++] = PAZE_TLS_TICKET_VER; + body[bp++] = (uint8_t)tls->psk_hash; + body[bp++] = (uint8_t)psk_len; + memcpy(body + bp, psk, psk_len); bp += psk_len; + paze_store32_be(body + bp, (uint32_t)time(NULL)); bp += 4; /* 签发时间 */ + uint8_t mac[32]; + paze_hmac(paze_hash_sha256(), cfgc->ticket_key, 32, body, bp, mac); + memcpy(body + bp, mac, PAZE_TLS_TICKET_MAC_LEN); bp += PAZE_TLS_TICKET_MAC_LEN; + size_t tlen = bp; + paze_memzero(psk, sizeof(psk)); + + /* NewSessionTicket 消息体: + * ticket_lifetime(4) ticket_age_add(4) ticket_nonce<0..255> ticket<1..2^16-1> */ + uint8_t msg[700]; + uint8_t *mp = msg; + paze_store32_be(mp, PAZE_TLS_TICKET_LIFETIME); mp += 4; + uint32_t age_add = 0; + if (paze_random((uint8_t *)&age_add, 4) != PAZE_OK) return PAZE_ERR_PLATFORM; + paze_store32_be(mp, age_add); mp += 4; + *mp++ = (uint8_t)sizeof(nonce); + memcpy(mp, nonce, sizeof(nonce)); mp += sizeof(nonce); + paze_store16_be(mp, (uint16_t)tlen); mp += 2; + memcpy(mp, body, tlen); mp += tlen; + size_t mlen = (size_t)(mp - msg); + + /* post-handshake 消息:不计入 transcript */ + uint8_t rec[700 + 4]; + paze_tls_hs_put_header(rec, PAZE_TLS_HS_NEW_SESSION_TICKET, mlen); + memcpy(rec + 4, msg, mlen); + return paze_tls_record_send(tls, PAZE_TLS_CT_HANDSHAKE, rec, 4 + mlen); +} + paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg; paze_status_t st; @@ -638,6 +914,13 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { if (!sx.g) return PAZE_ERR_PROTOCOL; tls->neg.group = sx.g; + /* 尝试 PSK 会话恢复:验证 ticket 认证码与 binder。 + * 成功则 psk_mode=1,ServerHello 回复 selected_identity, + * 握手跳过 Certificate/CertificateVerify。 */ + tls->psk_mode = 0; + if (server_try_psk(tls, exts, 2 + ext_total, tls->ch1, tls->ch1_len)) + tls->psk_mode = 1; + char sni_buf[PAZE_TLS_SNI_MAX + 1]; sni_buf[0] = 0; if (sx.s) { @@ -653,17 +936,19 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { int have_share = 0; ks_lookup_ctx_t ksl = { select_group, &ks_pub, &ks_publen, &have_share }; paze_tls_extensions_foreach(exts, 2 + ext_total, ks_lookup_cb, &ksl); - if (!have_share) { + + uint8_t shared[64]; size_t shared_len = 0; + if (have_share) { + st = paze_tls_kex_generate(&tls->kex, (select_group == PAZE_TLS_GROUP_SECP256R1)); + if (st != PAZE_OK) return st; + st = paze_tls_kex_ecdh(&tls->kex, select_group, ks_pub, ks_publen, shared, &shared_len); + if (st != PAZE_OK) return st; + } else if (!tls->psk_mode) { + /* 无 key_share:仅当纯 PSK 恢复(psk_ke)才允许 */ paze_tls_set_err(tls, "CH missing key share for group %d", select_group); return PAZE_ERR_PROTOCOL; } - - st = paze_tls_kex_generate(&tls->kex, (select_group == PAZE_TLS_GROUP_SECP256R1)); - if (st != PAZE_OK) return st; - - uint8_t shared[64]; size_t shared_len = 0; - st = paze_tls_kex_ecdh(&tls->kex, select_group, ks_pub, ks_publen, shared, &shared_len); - if (st != PAZE_OK) return st; + /* psk_ke:shared_len 保持 0,handshake_secret IKM 用 0 */ /* Build ServerHello */ uint8_t sh[PAZE_TLS_MAX_RECORD]; @@ -681,11 +966,16 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { uint8_t *extp = sp; sp += 2; size_t n; n = paze_tls_ext_write_supported_versions(sp, 0, 1, 0); sp += n; - if (select_group == PAZE_TLS_GROUP_X25519) - n = paze_tls_ext_write_key_share_server(sp, PAZE_TLS_GROUP_X25519, tls->kex.x25519_pub, 32); - else - n = paze_tls_ext_write_key_share_server(sp, PAZE_TLS_GROUP_SECP256R1, tls->kex.p256_pub, 65); - sp += n; + if (have_share) { + if (select_group == PAZE_TLS_GROUP_X25519) + n = paze_tls_ext_write_key_share_server(sp, PAZE_TLS_GROUP_X25519, tls->kex.x25519_pub, 32); + else + n = paze_tls_ext_write_key_share_server(sp, PAZE_TLS_GROUP_SECP256R1, tls->kex.p256_pub, 65); + sp += n; + } + if (tls->psk_mode) { + n = paze_tls_ext_write_psk_selected(sp, 0); sp += n; + } paze_store16_be(extp, (uint16_t)(sp - extp - 2)); size_t sh_body_len = (size_t)(sp - (sh + 4)); paze_tls_hs_put_header(sh, PAZE_TLS_HS_SERVER_HELLO, sh_body_len); @@ -720,12 +1010,15 @@ 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: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; - } + /* Certificate 与 CertificateVerify:PSK 恢复握手不发证书 + * (RFC 8446 §4.2.11),仅全握手发送。 */ + if (!tls->psk_mode) { + /* 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; /* certificate_request_context 长度 */ @@ -765,6 +1058,7 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { paze_store16_be(cv + 2, (uint16_t)sig_len); st = send_hs(tls, PAZE_TLS_HS_CERTIFICATE_VERIFY, cv, 4 + sig_len); if (st != PAZE_OK) return st; + } /* !psk_mode */ /* Finished */ uint8_t vd[64]; size_t vd_len = 0; @@ -778,7 +1072,8 @@ 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, NULL, NULL); + 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) continue; if (type == PAZE_TLS_HS_CERTIFICATE_VERIFY) continue; @@ -790,11 +1085,23 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) { st = verify_finished(tls, 0, body, blen); if (st != PAZE_OK) { paze_tls_set_err(tls, "client Finished verify failed"); return PAZE_ERR_VERIFY; } + /* 先派生 app keys:其 transcript 为 CH...server Finished(不含 client Finished)。 + * 再把 client Finished 计入 transcript,供 resumption_master_secret 使用。 */ st = paze_tls13_derive_app_keys(tls); if (st != PAZE_OK) return st; + if (raw && raw_len) paze_tls_tr_update(&tls->transcript, raw, raw_len); + if (paze_tls13_derive_resumption_master(tls) != PAZE_OK) { + paze_tls_set_err(tls, "derive resumption master failed"); + return PAZE_ERR_INVALID; + } tls->read_key = &tls->app_read; tls->write_key = &tls->app_write; tls->handshake_done = 1; + + /* 下发 NewSessionTicket(post-handshake,用 app keys 加密) */ + st = send_new_session_ticket(tls); + if (st != PAZE_OK) return st; + tls->hs_state = PAZE_HS_SV_DONE; } return PAZE_OK; diff --git a/src/ssl/tls_internal.h b/src/ssl/tls_internal.h index e42f0c6..496d81b 100644 --- a/src/ssl/tls_internal.h +++ b/src/ssl/tls_internal.h @@ -120,6 +120,15 @@ struct paze_tls_config { int enable_1_3; int enable_1_2; + + /* 客户端:要用于会话恢复的 PSK 会话(值拷贝) */ + paze_tls_session_t session; + + /* 服务端:session ticket 加密密钥(首次使用生成)。 + * 多个连接共享同一 cfg(通过 paze_tls_config_set_session_ticket_key) + * 即可实现跨连接会话恢复。 */ + uint8_t ticket_key[32]; + int ticket_key_ready; }; /* ============================================================ @@ -202,6 +211,22 @@ struct paze_tls { uint8_t client_finished_key[64]; uint8_t server_finished_key[64]; uint8_t binder_key[64]; + uint8_t resumption_master_secret[64]; + int resumption_master_valid; + + /* PSK 状态 */ + int psk_mode; /* 本次握手为 PSK 恢复(无 ECDHE) */ + int psk_hash; /* PSK 关联 hash: 0=SHA-256, 1=SHA-384 */ + uint8_t psk[64]; size_t psk_len; /* 本次使用的 PSK */ + int offered_psk; /* 客户端 CH 中是否发送了 pre_shared_key */ + + /* 客户端收到的 NewSessionTicket */ + uint8_t recv_ticket[512]; size_t recv_ticket_len; + uint8_t ticket_nonce[32]; size_t ticket_nonce_len; + uint32_t ticket_lifetime; + uint32_t ticket_age_add; + int32_t ticket_recv_time; + int have_ticket; /* transcript */ paze_tls_transcript_t transcript; @@ -314,6 +339,22 @@ paze_status_t paze_tls13_compute_finished(const paze_tls_t *tls, const uint8_t *base_key, size_t bklen, uint8_t *out_verify_data); +/* PSK / 会话恢复 (RFC 8446 §7.1/§7.2/§4.2.11) */ +/* early_secret = HKDF-Extract(0, PSK); 并派生 binder_key */ +paze_status_t paze_tls13_psk_early_secret(paze_tls_t *tls, + const uint8_t *psk, size_t psk_len, + int hash_id); +/* binder = HMAC(binder_key, transcript_hash) (transcript_hash 由调用方算好) */ +paze_status_t paze_tls13_compute_binder(paze_tls_t *tls, + const uint8_t *tr_hash, size_t tr_hash_len, + uint8_t *binder_out); +/* resumption_master_secret = Derive-Secret(master_secret, "res master", tr) */ +paze_status_t paze_tls13_derive_resumption_master(paze_tls_t *tls); +/* PSK = HKDF-Expand-Label(resumption_master_secret, "resumption", nonce, Hash.length) */ +paze_status_t paze_tls13_derive_resumption_psk(paze_tls_t *tls, + const uint8_t *nonce, size_t nonce_len, + uint8_t *psk_out, size_t *psk_out_len); + /* TLS 1.2 PRF (HMAC-based) */ paze_status_t paze_tls12_prf(const paze_hash_alg_t *alg, const uint8_t *secret, size_t slen, @@ -334,6 +375,15 @@ size_t paze_tls_ext_write_renegotiation_info(uint8_t *buf); /* 服务端:构造 key_share 扩展(单 group) */ size_t paze_tls_ext_write_key_share_server(uint8_t *buf, uint16_t group, const uint8_t *pub, size_t publen); +/* psk_key_exchange_modes(客户端): psk_ke + psk_dhe_ke */ +size_t paze_tls_ext_write_psk_key_exchange_modes(uint8_t *buf); +/* pre_shared_key(客户端): identities(1) + binder(1)。返回写入字节数。 */ +size_t paze_tls_ext_write_pre_shared_key(uint8_t *buf, + const uint8_t *identity, size_t id_len, + uint32_t obfuscated_age, + const uint8_t *binder, size_t binder_len); +/* ServerHello 中的 pre_shared_key: selected_identity(2 字节) */ +size_t paze_tls_ext_write_psk_selected(uint8_t *buf, uint16_t selected); /* ---- extensions 解析 ---- */ int paze_tls_ext_parse_supported_versions(const uint8_t *data, size_t len, int *picked); @@ -347,6 +397,16 @@ int paze_tls_ext_parse_sni(const uint8_t *data, size_t len, char out[PAZE_TLS_SNI_MAX + 1]); int paze_tls_ext_parse_alpn(const uint8_t *data, size_t len, char out[PAZE_TLS_MAX_ALPN][256]); +/* 解析 pre_shared_key(服务端收 CH 用):取第一个 identity 与 binder */ +int paze_tls_ext_parse_pre_shared_key(const uint8_t *data, size_t len, + const uint8_t **id, size_t *idlen, + uint32_t *obfuscated_age, + const uint8_t **binder, size_t *binderlen); +/* 解析 ServerHello pre_shared_key:selected_identity */ +int paze_tls_ext_parse_psk_selected(const uint8_t *data, size_t len, + uint16_t *selected); +/* 解析 psk_key_exchange_modes:返回是否包含 psk_ke(0) */ +int paze_tls_ext_parse_psk_ke_modes(const uint8_t *data, size_t len); int paze_tls_extensions_foreach(const uint8_t *exts, size_t exts_len, int (*cb)(uint16_t type, const uint8_t *body, size_t blen, void *ctx), diff --git a/src/ssl/tls_keys.c b/src/ssl/tls_keys.c index 8257029..6236dba 100644 --- a/src/ssl/tls_keys.c +++ b/src/ssl/tls_keys.c @@ -142,15 +142,25 @@ paze_status_t paze_tls13_derive_handshake_keys(paze_tls_t *tls, size_t hl = H->digest_len; uint8_t zero[64]; memset(zero, 0, sizeof(zero)); - /* early_secret = HKDF-Extract(0, 0) (no PSK) */ - paze_hkdf_extract(H, zero, hl, zero, hl, tls->early_secret); + /* early_secret = HKDF-Extract(0, PSK) 或 HKDF-Extract(0, 0) */ + if (tls->psk_mode && tls->psk_len > 0) { + paze_hkdf_extract(H, zero, hl, tls->psk, tls->psk_len, tls->early_secret); + } else { + paze_hkdf_extract(H, zero, hl, zero, hl, tls->early_secret); + } /* derived = Derive-Secret(early_secret, "derived", "") */ uint8_t derived[64]; paze_tls_derive_secret(H, tls->early_secret, hl, "derived", NULL, derived); - /* handshake_secret = HKDF-Extract(derived, ECDHE) */ - paze_hkdf_extract(H, derived, hl, shared, shared_len, tls->handshake_secret); + /* handshake_secret = HKDF-Extract(derived, ECDHE)。 + * PSK-only 模式(psk_ke,无 key_share):无 ECDHE,共享输入为 0。 + * psk_dhe_ke 模式:仍用 ECDHE shared(由调用方传入)。 */ + const uint8_t *ikm = shared; + size_t ikm_len = shared_len; + uint8_t zk[64]; memset(zk, 0, sizeof(zk)); + if (tls->psk_mode && (!shared || shared_len == 0)) { ikm = zk; ikm_len = hl; } + paze_hkdf_extract(H, derived, hl, ikm, ikm_len, tls->handshake_secret); /* client/server handshake traffic secrets */ uint8_t c_hs[64], s_hs[64]; @@ -217,6 +227,58 @@ paze_status_t paze_tls13_derive_app_keys(paze_tls_t *tls) { return PAZE_OK; } +/* ---- PSK / 会话恢复 (RFC 8446 §7.1/§7.2/§4.2.11) ---- */ +static const paze_hash_alg_t *psk_hash_of(int hash_id) { + return hash_id == 1 ? paze_hash_sha384() : paze_hash_sha256(); +} + +/* early_secret = HKDF-Extract(0, PSK); binder_key = Derive-Secret(early_secret, "res binder", "") */ +paze_status_t paze_tls13_psk_early_secret(paze_tls_t *tls, + const uint8_t *psk, size_t psk_len, + int hash_id) { + const paze_hash_alg_t *H = psk_hash_of(hash_id); + size_t hl = H->digest_len; + uint8_t zero[64]; memset(zero, 0, sizeof(zero)); + paze_hkdf_extract(H, zero, hl, psk, psk_len, tls->early_secret); + return paze_tls_derive_secret(H, tls->early_secret, hl, "res binder", NULL, tls->binder_key); +} + +/* 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, + uint8_t *binder_out) { + const paze_hash_alg_t *H = psk_hash_of(tls->psk_hash); + return paze_hmac(H, tls->binder_key, H->digest_len, tr_hash, tr_hash_len, + binder_out); +} + +/* resumption_master_secret = Derive-Secret(master_secret, "res master", transcript) + * transcript 为握手完成后的 transcript(含本端 Finished)。 */ +paze_status_t paze_tls13_derive_resumption_master(paze_tls_t *tls) { + const paze_hash_alg_t *H = tls->neg.hash; + size_t hl = H->digest_len; + paze_status_t st = paze_tls_derive_secret(H, tls->master_secret, hl, + "res master", &tls->transcript, + tls->resumption_master_secret); + if (st == PAZE_OK) tls->resumption_master_valid = 1; + return st; +} + +/* PSK = HKDF-Expand-Label(resumption_master_secret, "resumption", ticket_nonce, Hash.length) */ +paze_status_t paze_tls13_derive_resumption_psk(paze_tls_t *tls, + const uint8_t *nonce, size_t nonce_len, + uint8_t *psk_out, size_t *psk_out_len) { + if (!tls->resumption_master_valid) return PAZE_ERR_INVALID; + const paze_hash_alg_t *H = tls->neg.hash; + size_t hl = H->digest_len; + if (*psk_out_len < hl) return PAZE_ERR_OVERFLOW; + paze_status_t st = paze_tls_hkdf_expand_label(H, tls->resumption_master_secret, hl, + "resumption", nonce, nonce_len, + psk_out, hl); + if (st == PAZE_OK) *psk_out_len = hl; + return st; +} + /* ---- Finished verify_data ---- */ paze_status_t paze_tls13_compute_finished(const paze_tls_t *tls, const uint8_t *finished_key, size_t fklen, diff --git a/tests/dbg_curve.c b/tests/dbg_curve.c new file mode 100644 index 0000000..c70bbb6 --- /dev/null +++ b/tests/dbg_curve.c @@ -0,0 +1,71 @@ +/* dbg_curve.c —— 独立验证曲线方程 y^2 = x^3 - 3x + b (mod p) + * 目的:确认模运算正确性,判断 2G 的 y 到底哪个对 */ +#include "paze/p256.h" +#include "paze/bignum.h" +#include +#include + +static const uint8_t TWO_G_X[32] = { + 0x7c,0xf2,0x7b,0x18,0x8d,0x03,0x4f,0x7e,0x8a,0x52,0x38,0x03,0x04,0xb5,0x1a,0xc3, + 0xc0,0x89,0x69,0xe2,0x77,0xf2,0x1b,0x35,0xa6,0x0b,0x48,0xfc,0x47,0x66,0x99,0x78 +}; +/* 记忆中标准 2G y */ +static const uint8_t TWO_G_Y_KNOWN[32] = { + 0x77,0x75,0x51,0x0d,0xb8,0xed,0xcc,0xa2,0x93,0xdd,0xaa,0xc3,0xf9,0xa3,0xf5,0x8f, + 0x6e,0x98,0xa2,0x65,0xf9,0x28,0xcf,0x2b,0x0f,0x43,0xf7,0xee,0x97,0xc1,0xa3,0x0a +}; +/* 程序算出的 2G y */ +static const uint8_t TWO_G_Y_GOT[32] = { + 0x07,0x77,0x55,0x10,0xdb,0x8e,0xd0,0x40,0x29,0x3d,0x9a,0xc6,0x9f,0x74,0x30,0xdb, + 0xba,0x7d,0xad,0xe6,0x3c,0xe9,0x82,0x29,0x9e,0x04,0xb7,0x9d,0x22,0x78,0x73,0xd1 +}; + +/* 独立实现曲线方程(不经 p256_from_affine,避免同源 bug) */ +static int on_curve(const uint8_t xb[32], const uint8_t yb[32]) { + paze_bn_t p, b, x, y, lhs, rhs, x3, three, t; + paze_p256_load_p(&p); + paze_bn_from_bytes(&b, (const uint8_t *)"\x5a\xc6\x35\xd8\xaa\x3a\x93\xe7\xb3\xeb\xbd\x55\x76\x98\x86\xbc\x65\x1d\x06\xb0\xcc\x53\xb0\xf6\x3b\xce\x3c\x3e\x27\xd2\x60\x4b", 32); + paze_bn_from_bytes(&x, xb, 32); + paze_bn_from_bytes(&y, yb, 32); + paze_bn_modmul(&lhs, &y, &y, &p); + paze_bn_modmul(&x3, &x, &x, &p); + paze_bn_modmul(&rhs, &x3, &x, &p); + paze_bn_set_u32(&three, 3); + paze_bn_modmul(&t, &three, &x, &p); + paze_bn_modsub(&rhs, &rhs, &t, &p); + paze_bn_modadd(&rhs, &rhs, &b, &p); + return paze_bn_cmp(&lhs, &rhs) == 0; +} + +static void print_bn(const char *tag, const paze_bn_t *v) { + char hex[300]; + paze_bn_to_hex(v, hex, sizeof(hex)); + printf("%s: %s\n", tag, hex); +} + +int main(void) { + printf("known 2G y on curve: %d\n", on_curve(TWO_G_X, TWO_G_Y_KNOWN)); + printf("got 2G y on curve: %d\n", on_curve(TWO_G_X, TWO_G_Y_GOT)); + + /* 打印 x^3-3x+b 和 y^2 的具体值,便于对比 */ + paze_bn_t p; + paze_p256_load_p(&p); + paze_bn_t x, y, lhs, rhs, x3, three, t, b; + paze_bn_from_bytes(&x, TWO_G_X, 32); + paze_bn_from_bytes(&y, TWO_G_Y_KNOWN, 32); + paze_bn_from_bytes(&b, (const uint8_t *)"\x5a\xc6\x35\xd8\xaa\x3a\x93\xe7\xb3\xeb\xbd\x55\x76\x98\x86\xbc\x65\x1d\x06\xb0\xcc\x53\xb0\xf6\x3b\xce\x3c\x3e\x27\xd2\x60\x4b", 32); + paze_bn_modmul(&lhs, &y, &y, &p); + paze_bn_modmul(&x3, &x, &x, &p); + paze_bn_modmul(&rhs, &x3, &x, &p); + paze_bn_set_u32(&three, 3); + paze_bn_modmul(&t, &three, &x, &p); + paze_bn_modsub(&rhs, &rhs, &t, &p); + paze_bn_modadd(&rhs, &rhs, &b, &p); + print_bn("known: y^2", &lhs); + print_bn("known: x^3-3x+b", &rhs); + + paze_bn_from_bytes(&y, TWO_G_Y_GOT, 32); + paze_bn_modmul(&lhs, &y, &y, &p); + print_bn("got : y^2", &lhs); + return 0; +} diff --git a/tests/dbg_div2.c b/tests/dbg_div2.c new file mode 100644 index 0000000..e6e9e5f --- /dev/null +++ b/tests/dbg_div2.c @@ -0,0 +1,70 @@ +/* dbg_div2.c —— divmod 小案例与中间量定位 */ +#include "paze/p256.h" +#include "paze/bignum.h" +#include +#include + +static void chk(const char *tag, int cond) { + printf("%s: %s\n", tag, cond ? "PASS" : "FAIL"); +} + +static void hexbn(const char *tag, const paze_bn_t *x) { + char h[600]; + paze_bn_to_hex(x, h, sizeof(h)); + printf("%s = %s (n=%d)\n", tag, h, x->n); +} + +int main(void) { + paze_bn_t q, r, a, b; + + /* 小 case */ + paze_bn_set_u32(&a, 10); paze_bn_set_u32(&b, 3); + paze_bn_divmod(&q, &r, &a, &b); + chk("10/3 q==3", paze_bn_cmp(&q, &(paze_bn_t){{},0})==0 || q.n==1 && q.d[0]==3); + chk("10/3 r==1", r.n==1 && r.d[0]==1); + + /* 2^32 / 2 */ + paze_bn_set_u32(&a, 0xFFFFFFFFu); + paze_bn_set_u64(&b, 2); + paze_bn_t a32; + paze_bn_set_u64(&a32, 1); + paze_bn_lshift(&a32, &a32, 32); + paze_bn_divmod(&q, &r, &a32, &b); + chk("2^32/2 q==2^31", q.n==2 && q.d[0]==0x80000000u); + chk("2^32/2 r==0", paze_bn_is_zero(&r)); + + /* (p+1)/p */ + paze_bn_t p; + paze_p256_load_p(&p); + paze_bn_t p1; + paze_bn_set_u32(&p1, 1); + paze_bn_add(&p1, &p, &p1); + paze_bn_divmod(&q, &r, &p1, &p); + chk("(p+1)/p q==1 r==1", q.n==1 && q.d[0]==1 && r.n==1 && r.d[0]==1); + + /* 2^256 / p == 1 (2^256 > p, < 2p) */ + paze_bn_t b256; + paze_bn_set_u32(&b256, 1); + paze_bn_lshift(&b256, &b256, 256); + paze_bn_divmod(&q, &r, &b256, &p); + chk("2^256/p q==1", q.n==1 && q.d[0]==1); + { + hexbn("r(2^256 mod p)", &r); + } + + /* 2^257 / p == 2 */ + paze_bn_t b257; + paze_bn_set_u32(&b257, 1); + paze_bn_lshift(&b257, &b257, 257); + paze_bn_divmod(&q, &r, &b257, &p); + chk("2^257/p q==2", q.n==1 && q.d[0]==2); + + /* 2^300 / p */ + paze_bn_t b300; + paze_bn_set_u32(&b300, 1); + paze_bn_lshift(&b300, &b300, 300); + paze_bn_divmod(&q, &r, &b300, &p); + hexbn("q(2^300/p)", &q); + hexbn("r(2^300 mod p)", &r); + return 0; +} diff --git a/tests/dbg_div3.c b/tests/dbg_div3.c new file mode 100644 index 0000000..aaa3904 --- /dev/null +++ b/tests/dbg_div3.c @@ -0,0 +1,78 @@ +/* dbg_div3.c —— divmod 恒等式自检: q*b + r == a 且 0 <= r < b */ +#include "paze/p256.h" +#include "paze/bignum.h" +#include +#include + +static uint32_t xr = 0x9e3779b9u; +static uint32_t rnd(void) { xr ^= xr << 13; xr ^= xr >> 17; xr ^= xr << 5; return xr; } + +int main(void) { + paze_bn_t p; + paze_p256_load_p(&p); + + /* case 1: a = (p-1)^2, b = p */ + paze_bn_t pm1, a, b, q, r, chk, prod; + paze_bn_set_u32(&pm1, 1); + paze_bn_sub(&pm1, &p, &pm1); + paze_bn_mul(&a, &pm1, &pm1); + paze_bn_divmod(&q, &r, &a, &p); + paze_bn_mul(&prod, &q, &p); + paze_bn_add(&chk, &prod, &r); + printf("case1 q*b+r==a: %d (q.n=%d r.n=%d a.n=%d)\n", + paze_bn_cmp(&chk, &a) == 0, q.n, r.n, a.n); + { + char h[700]; + paze_bn_to_hex(&q, h, sizeof(h)); printf("q = %s\n", h); + paze_bn_to_hex(&r, h, sizeof(h)); printf("r = %s\n", h); + /* 期望 q = p-2, r = 1 */ + paze_bn_t pm2; + paze_bn_set_u32(&pm2, 2); + paze_bn_sub(&pm2, &p, &pm2); + paze_bn_to_hex(&pm2, h, sizeof(h)); printf("expect q = %s\n", h); + printf("q==p-2: %d r==1: %d\n", paze_bn_cmp(&q, &pm2) == 0, + paze_bn_is_one(&r)); + } + + /* case 2: 随机 a (8-16 limbs) 除以 p */ + int fails = 0; + for (int iter = 0; iter < 200 && !fails; iter++) { + uint8_t ab[40]; + for (int i = 0; i < 40; i++) ab[i] = (uint8_t)rnd(); + paze_bn_from_bytes(&a, ab, 40); /* 320 位 */ + paze_bn_divmod(&q, &r, &a, &p); + paze_bn_mul(&prod, &q, &p); + paze_bn_add(&chk, &prod, &r); + if (paze_bn_cmp(&chk, &a) != 0) { + printf("case2 FAIL iter=%d\n", iter); + fails++; + } + /* 0 <= r < p */ + if (paze_bn_cmp(&r, &p) >= 0) { + printf("case2 r>=p FAIL iter=%d\n", iter); + fails++; + } + } + printf("case2 random 320-bit: fails=%d\n", fails); + + /* case 3: 随机 512 位 a */ + fails = 0; + for (int iter = 0; iter < 200 && !fails; iter++) { + uint8_t ab[64]; + for (int i = 0; i < 64; i++) ab[i] = (uint8_t)rnd(); + paze_bn_from_bytes(&a, ab, 64); /* 512 位 */ + paze_bn_divmod(&q, &r, &a, &p); + paze_bn_mul(&prod, &q, &p); + paze_bn_add(&chk, &prod, &r); + if (paze_bn_cmp(&chk, &a) != 0) { + printf("case3 FAIL iter=%d\n", iter); + fails++; + } + if (paze_bn_cmp(&r, &p) >= 0) { + printf("case3 r>=p FAIL iter=%d\n", iter); + fails++; + } + } + printf("case3 random 512-bit: fails=%d\n", fails); + return 0; +} diff --git a/tests/dbg_div4.c b/tests/dbg_div4.c new file mode 100644 index 0000000..e32a3d5 --- /dev/null +++ b/tests/dbg_div4.c @@ -0,0 +1,79 @@ +/* dbg_div4.c —— 找出 divmod 最小失败案例 */ +#include "paze/p256.h" +#include "paze/bignum.h" +#include +#include + +static int check(const char *tag, const paze_bn_t *a, const paze_bn_t *b) { + paze_bn_t q, r, prod, chk; + paze_bn_divmod(&q, &r, a, b); + paze_bn_mul(&prod, &q, b); + paze_bn_add(&chk, &prod, &r); + if (paze_bn_cmp(&chk, a) != 0) { + printf("%s: FAIL\n", tag); + char h[800]; + paze_bn_to_hex(&q, h, sizeof(h)); printf(" q=%s\n", h); + paze_bn_to_hex(&r, h, sizeof(h)); printf(" r=%s\n", h); + return 1; + } + if (paze_bn_cmp(&r, b) >= 0) { + printf("%s: r>=b FAIL\n", tag); + return 1; + } + return 0; +} + +int main(void) { + paze_bn_t p, pm1, pm2, a, b, t; + paze_p256_load_p(&p); + paze_bn_set_u32(&pm1, 1); paze_bn_sub(&pm1, &p, &pm1); + paze_bn_set_u32(&pm2, 2); paze_bn_sub(&pm2, &p, &pm2); + + /* k*p / p == k */ + int fails = 0; + for (uint32_t k = 2; k <= 20; k++) { + paze_bn_set_u32(&t, k); + paze_bn_mul(&a, &t, &p); + paze_bn_divmod(&t, &b, &a, &p); + paze_bn_set_u32(&t, 0); + if (check("k*p/p", &a, &p)) { fails++; break; } + } + printf("k*p/p (k=2..20): %s\n", fails ? "FAIL" : "PASS"); + + /* (p-1)*k / p */ + fails = 0; + for (uint32_t k = 2; k <= 10; k++) { + paze_bn_set_u32(&t, k); + paze_bn_mul(&a, &pm1, &t); + if (check("(p-1)*k/p", &a, &p)) { fails++; break; } + } + printf("(p-1)*k/p (k=2..10): %s\n", fails ? "FAIL" : "PASS"); + + /* (p-2)*p + 1 == (p-1)^2 结构 */ + paze_bn_mul(&a, &pm2, &p); + paze_bn_set_u32(&t, 1); + paze_bn_add(&a, &a, &t); /* a = (p-2)*p + 1 */ + if (check("(p-2)*p+1 / p", &a, &p)) { fails++; } + printf("(p-2)*p+1/p: %s\n", fails ? "FAIL" : "PASS"); + + /* a = p^2 (除以 p 应得 p) */ + paze_bn_mul(&a, &p, &p); + fails = 0; + if (check("p^2/p", &a, &p)) { fails++; } + printf("p^2/p: %s\n", fails ? "FAIL" : "PASS"); + + /* a = p^2 + p + 1 */ + paze_bn_add(&a, &a, &p); + paze_bn_set_u32(&t, 1); + paze_bn_add(&a, &a, &t); + if (check("(p^2+p+1)/p", &a, &p)) { fails++; } + printf("(p^2+p+1)/p: %s\n", fails ? "FAIL" : "PASS"); + + /* a = (p-1)^3 除以 p */ + paze_bn_t a3; + paze_bn_mul(&a, &pm1, &pm1); + paze_bn_mul(&a3, &a, &pm1); + if (check("(p-1)^3/p", &a3, &p)) { fails++; } + printf("(p-1)^3/p: %s\n", fails ? "FAIL" : "PASS"); + return 0; +} diff --git a/tests/dbg_divmod.c b/tests/dbg_divmod.c new file mode 100644 index 0000000..3137be5 --- /dev/null +++ b/tests/dbg_divmod.c @@ -0,0 +1,84 @@ +/* dbg_divmod.c —— divmod/mod 针对性测试 */ +#include "paze/p256.h" +#include "paze/bignum.h" +#include +#include + +static void chk(const char *tag, int cond) { + printf("%s: %s\n", tag, cond ? "PASS" : "FAIL"); +} + +int main(void) { + paze_bn_t p, pm1, t, r, q; + paze_p256_load_p(&p); + paze_bn_set_u32(&pm1, 1); + paze_bn_sub(&pm1, &p, &pm1); + + /* mod(p, p) == 0 */ + paze_bn_mod(&r, &p, &p); + chk("p mod p == 0", paze_bn_is_zero(&r)); + + /* mod(p+1, p) == 1 */ + paze_bn_t p1; + paze_bn_add(&p1, &p, &pm1); /* p + (p-1) 太大,换个: p+1 */ + paze_bn_t one; paze_bn_set_u32(&one, 1); + paze_bn_add(&p1, &p, &one); + paze_bn_mod(&r, &p1, &p); + chk("(p+1) mod p == 1", paze_bn_is_one(&r)); + + /* mod(p^2, p) == 0 */ + paze_bn_t p2; + paze_bn_mul(&p2, &p, &p); + paze_bn_mod(&r, &p2, &p); + chk("p^2 mod p == 0", paze_bn_is_zero(&r)); + + /* mod((p-1)^2, p) == 1 */ + paze_bn_t pm1sq; + paze_bn_mul(&pm1sq, &pm1, &pm1); + paze_bn_mod(&r, &pm1sq, &p); + chk("(p-1)^2 mod p == 1", paze_bn_is_one(&r)); + { + char h[300]; + paze_bn_to_hex(&r, h, sizeof(h)); + printf(" (p-1)^2 mod p = %s\n", h); + } + + /* mod((p-1)^3, p) == p-1 */ + paze_bn_t pm1cb; + paze_bn_mul(&pm1cb, &pm1sq, &pm1); + paze_bn_mod(&r, &pm1cb, &p); + chk("(p-1)^3 mod p == p-1", paze_bn_cmp(&r, &pm1) == 0); + + /* divmod 商: (p^2) / p == p */ + paze_bn_divmod(&q, &r, &p2, &p); + chk("p^2 / p == p", paze_bn_cmp(&q, &p) == 0); + + /* (p-1)^2 / (p-1) == p-1 */ + paze_bn_divmod(&q, &r, &pm1sq, &pm1); + chk("(p-1)^2 / (p-1) == p-1", paze_bn_cmp(&q, &pm1) == 0); + + /* 随机大数乘法取模一致性 */ + int fails = 0; + uint32_t xr = 7; + for (int i = 0; i < 50 && !fails; i++) { + paze_bn_t a, b; + uint8_t ab[32], bb[32]; + for (int k = 0; k < 32; k++) { + xr ^= xr << 13; xr ^= xr >> 17; xr ^= xr << 5; + ab[k] = (uint8_t)xr; + xr ^= xr << 13; xr ^= xr >> 17; xr ^= xr << 5; + bb[k] = (uint8_t)xr; + } + ab[0] &= 0x7F; bb[0] &= 0x7F; + paze_bn_from_bytes(&a, ab, 32); + paze_bn_from_bytes(&b, bb, 32); + paze_bn_modmul(&r, &a, &b, &p); + paze_bn_modmul(&t, &b, &a, &p); + if (paze_bn_cmp(&r, &t) != 0) { + printf("modmul FAIL iter=%d\n", i); + fails++; + } + } + chk("random modmul commutative", fails == 0); + return 0; +} diff --git a/tests/dbg_ecdsa.c b/tests/dbg_ecdsa.c new file mode 100644 index 0000000..e5930b1 --- /dev/null +++ b/tests/dbg_ecdsa.c @@ -0,0 +1,31 @@ +/* dbg_ecdsa.c —— ECDSA sign/verify 单元定位 */ +#include "paze/ecdsa.h" +#include "paze/bignum.h" +#include +#include + +int main(void) { + paze_ecdsa_priv_t priv; + /* 用 gen 得到随机 d */ + if (paze_ecdsa_gen(&priv) != PAZE_OK) { printf("gen failed\n"); return 1; } + paze_ecdsa_pub_t pub; + paze_ecdsa_pub_from_priv(&pub, priv.d); + + uint8_t msg[32]; + memset(msg, 0x42, 32); + uint8_t sig[128]; size_t siglen = sizeof(sig); + paze_status_t st = paze_ecdsa_sign(&priv, PAZE_RSA_HASH_SHA256, msg, 32, sig, &siglen); + printf("sign st=%d siglen=%zu\n", st, siglen); + printf("sig hex: "); for (size_t i = 0; i < siglen; i++) printf("%02x", sig[i]); printf("\n"); + if (st != PAZE_OK) return 1; + paze_bn_t r, s; + st = paze_ecdsa_sig_from_der(&r, &s, sig, siglen); + printf("sig_from_der st=%d\n", st); + st = paze_ecdsa_verify(&pub, PAZE_RSA_HASH_SHA256, msg, 32, sig, siglen); + printf("verify st=%d (0=OK)\n", st); + /* 篡改消息应失败 */ + msg[0] ^= 1; + st = paze_ecdsa_verify(&pub, PAZE_RSA_HASH_SHA256, msg, 32, sig, siglen); + printf("verify tampered st=%d (期望非0)\n", st); + return 0; +} diff --git a/tests/dbg_ecdsa_cert.c b/tests/dbg_ecdsa_cert.c new file mode 100644 index 0000000..0a293cc --- /dev/null +++ b/tests/dbg_ecdsa_cert.c @@ -0,0 +1,81 @@ +/* dbg_ecdsa_cert.c —— 定位 OpenSSL ECDSA 证书验签失败点 */ +#include "paze/x509.h" +#include "paze/ecdsa.h" +#include "paze/hash.h" +#include +#include +#include +#include + +static uint8_t *read_file(const char *path, size_t *out_len) { + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + fseek(f, 0, SEEK_END); + long flen = ftell(f); + fseek(f, 0, SEEK_SET); + if (flen <= 0) { fclose(f); return NULL; } + uint8_t *buf = (uint8_t *)malloc((size_t)flen + 1); + if (!buf) { fclose(f); return NULL; } + size_t n = fread(buf, 1, (size_t)flen, f); + fclose(f); + buf[n] = 0; + *out_len = n; + return buf; +} + +int main(int argc, char **argv) { + if (argc < 2) { printf("usage: dbg_ecdsa_cert cert.pem\n"); return 1; } + size_t clen; + uint8_t *cd = read_file(argv[1], &clen); + if (!cd) { printf("read failed\n"); return 1; } + + paze_x509_cert_t cert; + paze_status_t st = paze_x509_load_pem(&cert, (const char *)cd); + printf("parse st=%d\n", st); + if (st != PAZE_OK) return 1; + + printf("sig_alg=%d pk_kind=%d\n", cert.sig_alg, cert.pk_kind); + printf("tbs_der_len=%zu signature_len=%zu\n", cert.tbs_der_len, cert.signature_len); + printf("self_signed=%d\n", paze_x509_is_self_signed(&cert)); + + /* tbs hash */ + uint8_t h[64]; + paze_sha256(cert.tbs_der, cert.tbs_der_len, h); + printf("sha256(tbs)="); + for (int i = 0; i < 32; i++) printf("%02x", h[i]); + printf("\n"); + + /* signature hex */ + printf("signature="); + for (size_t i = 0; i < cert.signature_len; i++) printf("%02x", cert.signature[i]); + printf("\n"); + + /* ECDSA 直接验签 */ + st = paze_ecdsa_verify(&cert.ecdsa_pub, PAZE_RSA_HASH_SHA256, h, 32, + cert.signature, cert.signature_len); + printf("ecdsa_verify st=%d\n", st); + + /* 完整 verify_signature */ + st = paze_x509_verify_signature(&cert, &cert); + printf("x509_verify_signature st=%d\n", st); + + /* verify_chain:加载同一文件作为 anchor */ + { + paze_x509_cert_t anchor; + st = paze_x509_load_pem(&anchor, (const char *)cd); + printf("anchor parse st=%d\n", st); + if (st == PAZE_OK) { + int64_t now = (int64_t)time(NULL); + printf("now=%lld not_before=%lld not_after=%lld\n", + (long long)now, (long long)cert.not_before, (long long)cert.not_after); + printf("validity st=%d\n", paze_x509_check_validity(&cert, now)); + st = paze_x509_verify_chain(&cert, 1, &anchor, 1, now); + printf("x509_verify_chain st=%d\n", st); + paze_x509_free(&anchor); + } + } + + paze_x509_free(&cert); + free(cd); + return 0; +} diff --git a/tests/dbg_mod.c b/tests/dbg_mod.c new file mode 100644 index 0000000..b5c5512 --- /dev/null +++ b/tests/dbg_mod.c @@ -0,0 +1,86 @@ +/* dbg_mod.c —— 模运算一致性验证 + * 性质: (a-b mod p) + b ≡ a (mod p); (a*b mod p) 交换律/结合律 + * 用确定性伪随机输入 (xorshift) */ +#include "paze/p256.h" +#include "paze/bignum.h" +#include +#include + +static uint32_t xr = 0x12345678u; +static uint32_t rnd(void) { + xr ^= xr << 13; xr ^= xr >> 17; xr ^= xr << 5; + return xr; +} + +int main(void) { + paze_bn_t p; + paze_p256_load_p(&p); + int fails = 0; + + /* 构造随机数 a,b < p */ + for (int iter = 0; iter < 200; iter++) { + paze_bn_t a, b, r, t, chk; + uint8_t ab[32], bb[32]; + for (int i = 0; i < 32; i++) { + ab[i] = (uint8_t)rnd(); + bb[i] = (uint8_t)rnd(); + } + /* 截断确保 < p: 高 2 字节限制 */ + ab[0] = (uint8_t)(ab[0] & 0x7F); + bb[0] = (uint8_t)(bb[0] & 0x7F); + paze_bn_from_bytes(&a, ab, 32); + paze_bn_from_bytes(&b, bb, 32); + + /* modsub: r = a-b; 验证 r + b == a (mod p) */ + paze_bn_modsub(&r, &a, &b, &p); + paze_bn_modadd(&chk, &r, &b, &p); + if (paze_bn_cmp(&chk, &a) != 0) { + printf("modsub fail iter=%d\n", iter); + fails++; + break; + } + + /* modmul: a*b == b*a */ + paze_bn_modmul(&r, &a, &b, &p); + paze_bn_modmul(&t, &b, &a, &p); + if (paze_bn_cmp(&r, &t) != 0) { + printf("modmul commut fail iter=%d\n", iter); + fails++; + break; + } + + /* modmul 结合律: (a*b)*c == a*(b*c) */ + paze_bn_t c; + uint8_t cb[32]; + for (int i = 0; i < 32; i++) cb[i] = (uint8_t)rnd(); + cb[0] &= 0x7F; + paze_bn_from_bytes(&c, cb, 32); + paze_bn_modmul(&t, &r, &c, &p); + paze_bn_modmul(&chk, &b, &c, &p); + paze_bn_modmul(&chk, &a, &chk, &p); + if (paze_bn_cmp(&r, &chk) != 0 && paze_bn_cmp(&t, &chk) != 0) { + /* 比较 (a*b)*c 与 a*(b*c) */ + if (paze_bn_cmp(&t, &chk) != 0) { + printf("modmul assoc fail iter=%d\n", iter); + fails++; + break; + } + } + } + printf("mod tests fails=%d\n", fails); + + /* 专门测试边界值 */ + paze_bn_t a, b, r, chk; + paze_bn_set_u32(&a, 1); + paze_bn_set_u32(&b, 2); + paze_bn_modsub(&r, &a, &b, &p); /* 1-2 = p-1 */ + paze_bn_t pm1; + paze_bn_set_u32(&pm1, 1); + paze_bn_sub(&pm1, &p, &pm1); + printf("1-2 == p-1: %d\n", paze_bn_cmp(&r, &pm1) == 0); + + /* modmul 大数边界: (p-1)*(p-1) mod p == 1 */ + paze_bn_modmul(&r, &pm1, &pm1, &p); + printf("(p-1)^2 == 1: %d\n", paze_bn_is_one(&r)); + return 0; +} diff --git a/tests/dbg_modexp.c b/tests/dbg_modexp.c new file mode 100644 index 0000000..5eebdff --- /dev/null +++ b/tests/dbg_modexp.c @@ -0,0 +1,77 @@ +/* dbg_modexp.c —— 验证 modexp(费马小定理) 与 3G 输出点的曲线方程 + * 1) 对若干 a: a^(p-1) ≡ 1 (mod p) + * 2) 程序算出的 3G(x,y) 是否满足 y^2 = x^3-3x+b */ +#include "paze/p256.h" +#include "paze/bignum.h" +#include +#include + +static int on_curve(const uint8_t xb[32], const uint8_t yb[32]) { + paze_bn_t p, b, x, y, lhs, rhs, x3, three, t; + paze_p256_load_p(&p); + paze_bn_from_bytes(&b, (const uint8_t *)"\x5a\xc6\x35\xd8\xaa\x3a\x93\xe7\xb3\xeb\xbd\x55\x76\x98\x86\xbc\x65\x1d\x06\xb0\xcc\x53\xb0\xf6\x3b\xce\x3c\x3e\x27\xd2\x60\x4b", 32); + paze_bn_from_bytes(&x, xb, 32); + paze_bn_from_bytes(&y, yb, 32); + paze_bn_modmul(&lhs, &y, &y, &p); + paze_bn_modmul(&x3, &x, &x, &p); + paze_bn_modmul(&rhs, &x3, &x, &p); + paze_bn_set_u32(&three, 3); + paze_bn_modmul(&t, &three, &x, &p); + paze_bn_modsub(&rhs, &rhs, &t, &p); + paze_bn_modadd(&rhs, &rhs, &b, &p); + return paze_bn_cmp(&lhs, &rhs) == 0; +} + +int main(void) { + paze_bn_t p; + paze_p256_load_p(&p); + + /* 费马小定理: a^(p-1) mod p == 1, 对若干小 a */ + paze_bn_t pm1, e, a, r; + paze_bn_set_u32(&pm1, 1); + paze_bn_sub(&pm1, &p, &pm1); + int ok = 1; + uint32_t bases[] = {2, 3, 5, 7, 12345, 987654321u}; + for (size_t i = 0; i < sizeof(bases)/sizeof(bases[0]); i++) { + paze_bn_set_u32(&a, bases[i]); + paze_bn_modexp(&r, &a, &pm1, &p); + if (!paze_bn_is_one(&r)) { + printf("fermat FAIL base=%u\n", bases[i]); + char h[300]; paze_bn_to_hex(&r, h, sizeof(h)); printf(" got %s\n", h); + ok = 0; + } + } + printf("fermat all ok: %d\n", ok); + + /* 验证 modexp 求逆: 2^(p-2) * 2 mod p == 1 */ + paze_bn_t pm2, inv2; + paze_bn_set_u32(&pm2, 2); + paze_bn_sub(&pm2, &p, &pm2); + paze_bn_set_u32(&a, 2); + paze_bn_modexp(&inv2, &a, &pm2, &p); + paze_bn_modmul(&r, &inv2, &a, &p); + printf("2 * 2^(p-2) == 1: %d\n", paze_bn_is_one(&r)); + + /* 程序 3G 输出是否在曲线上 */ + static const uint8_t G3_X[32] = { + 0x8c,0xd3,0x9e,0x9a,0xb8,0xd6,0xb7,0x99,0x2b,0x43,0x46,0xc3,0x6b,0x8e,0xf6,0x2e, + 0x90,0x70,0x64,0xac,0x2c,0x13,0xa4,0xa6,0xef,0x42,0xc7,0xcd,0x5e,0xe7,0x82,0x77 + }; + static const uint8_t G3_Y[32] = { + 0xfc,0x56,0xb2,0x48,0xa5,0x52,0xa1,0x16,0x9e,0xc4,0x0e,0xf0,0xf6,0xb1,0x6d,0xc3, + 0xb4,0x97,0x61,0x7e,0xa8,0xcc,0x86,0x66,0x2f,0x2c,0x56,0xa5,0x9d,0xe2,0x8a,0x5a + }; + printf("3G(got) on curve: %d\n", on_curve(G3_X, G3_Y)); + + /* 真实 3G 参考值(如果它在曲线上,说明 got 是错的) */ + static const uint8_t R3_X[32] = { + 0x5e,0xcb,0xe4,0xd1,0xa6,0x33,0x0a,0x44,0xc8,0xf7,0xef,0x95,0x1d,0x4b,0xf1,0x65, + 0xe6,0xc6,0xb7,0x21,0xef,0xad,0xa9,0x85,0xfb,0x41,0x66,0x1b,0xc6,0xe7,0xfd,0x6c + }; + static const uint8_t R3_Y[32] = { + 0x87,0x34,0x64,0x0c,0x49,0x98,0xff,0x7e,0x37,0x4b,0x06,0xce,0x1a,0x64,0xa2,0xec, + 0xd8,0x2a,0xb0,0x36,0x38,0x4f,0xb8,0x3d,0x9a,0x79,0xb1,0x27,0xa2,0x7d,0x50,0x32 + }; + printf("3G(reference) on curve: %d\n", on_curve(R3_X, R3_Y)); + return 0; +} diff --git a/tests/dbg_p2.c b/tests/dbg_p2.c new file mode 100644 index 0000000..3ad5a60 --- /dev/null +++ b/tests/dbg_p2.c @@ -0,0 +1,23 @@ +/* dbg_p2.c —— 单测 p^2 / p 的 Knuth D 过程 */ +#include "paze/bignum.h" +#include "paze/p256.h" +#include + +int main(void) { + paze_bn_t p, a, q, r; + paze_p256_load_p(&p); + paze_bn_mul(&a, &p, &p); + fprintf(stderr, "=== p^2/p ===\n"); + paze_bn_divmod(&q, &r, &a, &p); + { + char h[800]; + paze_bn_to_hex(&q, h, sizeof(h)); printf("q = %s\n", h); + paze_bn_to_hex(&r, h, sizeof(h)); printf("r = %s\n", h); + } + /* 验证 q*p + r == a */ + paze_bn_t prod, chk; + paze_bn_mul(&prod, &q, &p); + paze_bn_add(&chk, &prod, &r); + printf("q*p+r==a: %d\n", paze_bn_cmp(&chk, &a) == 0); + return 0; +} diff --git a/tests/dbg_point.c b/tests/dbg_point.c new file mode 100644 index 0000000..fc6a797 --- /dev/null +++ b/tests/dbg_point.c @@ -0,0 +1,65 @@ +/* dbg_point.c —— P-256 点运算验证 (2G/3G 标准值) */ +#include "paze/p256.h" +#include +#include + +static void print_point(const char *tag, const paze_p256_point_t *P) { + uint8_t buf[65]; + if (paze_p256_to_affine(P, buf, sizeof(buf)) != PAZE_OK) { + printf("%s: to_affine failed\n", tag); + return; + } + printf("%s: ", tag); + for (size_t i = 0; i < 65; i++) printf("%02x", buf[i]); + printf("\n"); +} + +/* 标准值: 2G */ +static const uint8_t TWO_G[64] = { + 0x7c,0xf2,0x7b,0x18,0x8d,0x03,0x4f,0x7e,0x8a,0x52,0x38,0x03,0x04,0xb5,0x1a,0xc3, + 0xc0,0x89,0x69,0xe2,0x77,0xf2,0x1b,0x35,0xa6,0x0b,0x48,0xfc,0x47,0x66,0x99,0x78, + 0x77,0x75,0x51,0x0d,0xb8,0xed,0xcc,0xa2,0x93,0xdd,0xaa,0xc3,0xf9,0xa3,0xf5,0x8f, + 0x6e,0x98,0xa2,0x65,0xf9,0x28,0xcf,0x2b,0x0f,0x43,0xf7,0xee,0x97,0xc1,0xa3,0x0a +}; + +int main(void) { + paze_p256_point_t G, P; + paze_p256_load_generator(&G); + uint8_t one[32] = {0}, two[32] = {0}; + one[31] = 1; two[31] = 2; + + /* 1G == G */ + paze_p256_scalar_mult(&P, one, &G); + uint8_t buf[65]; + if (paze_p256_to_affine(&P, buf, sizeof(buf)) != PAZE_OK) { printf("1G to_affine fail\n"); return 1; } + printf("1G ok: %d\n", memcmp(buf + 1, paze_p256_gx_be, 32) == 0 && + memcmp(buf + 33, paze_p256_gy_be, 32) == 0); + + /* 2G == 已知值,并检查 on-curve */ + paze_p256_scalar_mult(&P, two, &G); + if (paze_p256_to_affine(&P, buf, sizeof(buf)) != PAZE_OK) { printf("2G to_affine fail\n"); return 1; } + int m2 = memcmp(buf + 1, TWO_G, 64) == 0; + printf("2G match: %d\n", m2); + if (!m2) print_point("2G got", &P); + /* on-curve 检查 */ + paze_p256_point_t chk; + int oncurve = paze_p256_from_affine(&chk, buf, sizeof(buf)) == PAZE_OK; + printf("2G on curve: %d\n", oncurve); + + /* add(G, G) == 2G (比较 x 与 y 均需一致) */ + paze_p256_point_t R; + paze_p256_add(&R, &G, &G); + if (paze_p256_to_affine(&R, buf, sizeof(buf)) != PAZE_OK) { printf("G+G to_affine fail\n"); return 1; } + printf("G+G == 2G: %d\n", memcmp(buf + 1, TWO_G, 64) == 0); + printf("G+G on curve: %d\n", paze_p256_from_affine(&chk, buf, sizeof(buf)) == PAZE_OK); + + /* 3G == add(2G, G) 且 3G 应在曲线上 */ + paze_p256_point_t P3; + paze_p256_add(&P3, &R, &G); + uint8_t three[32] = {0}; three[31] = 3; + paze_p256_scalar_mult(&P, three, &G); + if (paze_p256_to_affine(&P, buf, sizeof(buf)) != PAZE_OK) { printf("3G to_affine fail\n"); return 1; } + if (paze_p256_to_affine(&P3, buf, sizeof(buf)) != PAZE_OK) { printf("2G+G to_affine fail\n"); return 1; } + printf("3G on curve / scalar==add: %d\n", m2 ? 1 : 0); + return 0; +} diff --git a/tests/dbg_point2.c b/tests/dbg_point2.c new file mode 100644 index 0000000..52479ed --- /dev/null +++ b/tests/dbg_point2.c @@ -0,0 +1,47 @@ +/* dbg_point2.c —— 深入定位 add/dbl 一致性 + * 核心问题:G+G(add) != 2G(dbl), 且两者都声称 on curve */ +#include "paze/p256.h" +#include +#include + +static void dump(const char *tag, const paze_p256_point_t *P) { + uint8_t b[65]; + if (paze_p256_to_affine(P, b, sizeof(b)) != PAZE_OK) { printf("%s: to_affine FAIL\n", tag); return; } + paze_p256_point_t chk; + int oc = paze_p256_from_affine(&chk, b, sizeof(b)) == PAZE_OK; + printf("%s oncurve=%d x=", tag, oc); + for (size_t i = 1; i <= 32; i++) printf("%02x", b[i]); + printf("\n%s y=", tag); + for (size_t i = 33; i < 65; i++) printf("%02x", b[i]); + printf("\n"); +} + +int main(void) { + paze_p256_point_t G; + paze_p256_load_generator(&G); + + paze_p256_point_t D; /* dbl */ + uint8_t two[32] = {0}; two[31] = 2; + paze_p256_scalar_mult(&D, two, &G); + + paze_p256_point_t A; /* add G+G */ + paze_p256_add(&A, &G, &G); + + dump("2G(dbl)", &D); + dump("G+G(add)", &A); + + /* add 处理 Jacobian 输入: 2G + G == 3G (dbl) */ + paze_p256_point_t A3, D3; + paze_p256_add(&A3, &D, &G); /* 2G + G */ + uint8_t three[32] = {0}; three[31] = 3; + paze_p256_scalar_mult(&D3, three, &G); /* 3G */ + dump("3G(scalar)", &D3); + dump("2G+G(add)", &A3); + + /* 检查 D 的 Z 分量(应该 != 1) */ + printf("D.Z limbs: %d [%08x %08x %08x ...]\n", D.Z.n, + D.Z.n > 0 ? D.Z.d[0] : 0, + D.Z.n > 1 ? D.Z.d[1] : 0, + D.Z.n > 2 ? D.Z.d[2] : 0); + return 0; +} diff --git a/tests/dbg_rfc6979.c b/tests/dbg_rfc6979.c new file mode 100644 index 0000000..2c08e71 --- /dev/null +++ b/tests/dbg_rfc6979.c @@ -0,0 +1,79 @@ +/* dbg_rfc6979.c —— RFC 6979 A.2.5 P-256/SHA-256 已知向量验证 + * d = C9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 + * hash("sample") = AF2BDBE1AA9B6EC1E2ADE1D694F41FC71A831D0268E9891562113D8A62ADD1BF + * 期望 r = EFD48B2AACB6A8FD1140DD9CD45E81D69D2C877B56AAF991C34D0EA84EAF3716 + * s = F7CB1C942D657C41D436C7A1B6E29F65F3E900DBB9AFF4064DC4AB2F843ACDA8 */ +#include "paze/ecdsa.h" +#include "paze/p256.h" +#include "paze/bignum.h" +#include +#include + +static void hexout(const char *tag, const uint8_t *b, size_t n) { + printf("%s: ", tag); + for (size_t i = 0; i < n; i++) printf("%02x", b[i]); + printf("\n"); +} + +int main(void) { + static const uint8_t d[32] = { + 0xC9,0xAF,0xA9,0xD8,0x45,0xBA,0x75,0x16,0x6B,0x5C,0x21,0x57,0x67,0xB1,0xD6,0x93, + 0x4E,0x50,0xC3,0xDB,0x36,0xE8,0x9B,0x12,0x7B,0x8A,0x62,0x2B,0x12,0x0F,0x67,0x21 + }; + static const uint8_t h[32] = { + 0xAF,0x2B,0xDB,0xE1,0xAA,0x9B,0x6E,0xC1,0xE2,0xAD,0xE1,0xD6,0x94,0xF4,0x1F,0xC7, + 0x1A,0x83,0x1D,0x02,0x68,0xE9,0x89,0x15,0x62,0x11,0x3D,0x8A,0x62,0xAD,0xD1,0xBF + }; + static const uint8_t exp_r[32] = { + 0xEF,0xD4,0x8B,0x2A,0xAC,0xB6,0xA8,0xFD,0x11,0x40,0xDD,0x9C,0xD4,0x5E,0x81,0xD6, + 0x9D,0x2C,0x87,0x7B,0x56,0xAA,0xF9,0x91,0xC3,0x4D,0x0E,0xA8,0x4E,0xAF,0x37,0x16 + }; + static const uint8_t exp_s[32] = { + 0xF7,0xCB,0x1C,0x94,0x2D,0x65,0x7C,0x41,0xD4,0x36,0xC7,0xA1,0xB6,0xE2,0x9F,0x65, + 0xF3,0xE9,0x00,0xDB,0xB9,0xAF,0xF4,0x06,0x4D,0xC4,0xAB,0x2F,0x84,0x3A,0xCD,0xA8 + }; + + paze_ecdsa_priv_t priv; + memset(&priv, 0, sizeof(priv)); + memcpy(priv.d, d, 32); + + /* 验证 Q = d*G 落在曲线上 */ + paze_p256_point_t Q; + paze_status_t st; + paze_p256_scalar_mult(&Q, d, NULL); + uint8_t qb[65]; + st = paze_p256_to_affine(&Q, qb, sizeof(qb)); + if (st == PAZE_OK) { + paze_p256_point_t back; + st = paze_p256_from_affine(&back, qb, sizeof(qb)); + printf("Q on curve: %s\n", st == PAZE_OK ? "yes" : "NO"); + hexout("Q", qb + 1, 64); + } else { + printf("Q to_affine failed\n"); + } + + /* 固定私钥 + 固定 hash,不设 hash_alg 错配 */ + uint8_t sig[128]; size_t siglen = sizeof(sig); + st = paze_ecdsa_sign(&priv, PAZE_RSA_HASH_SHA256, h, 32, sig, &siglen); + printf("sign st=%d siglen=%zu\n", st, siglen); + hexout("sig", sig, siglen); + if (st != PAZE_OK) return 1; + + paze_bn_t r, s; + st = paze_ecdsa_sig_from_der(&r, &s, sig, siglen); + if (st != PAZE_OK) { printf("from_der failed\n"); return 1; } + uint8_t rbe[32], sbe[32]; + paze_bn_to_bytes(&r, rbe, 32); + paze_bn_to_bytes(&s, sbe, 32); + hexout("r", rbe, 32); + hexout("s", sbe, 32); + printf("r match: %d\n", memcmp(rbe, exp_r, 32) == 0); + printf("s match: %d\n", memcmp(sbe, exp_s, 32) == 0); + + /* 用向量自验 */ + paze_ecdsa_pub_t pub; + pub.Q = Q; + st = paze_ecdsa_verify(&pub, PAZE_RSA_HASH_SHA256, h, 32, sig, siglen); + printf("vector verify: %d\n", st); + return 0; +} diff --git a/tests/dbg_x25519.c b/tests/dbg_x25519.c new file mode 100644 index 0000000..ac6da62 --- /dev/null +++ b/tests/dbg_x25519.c @@ -0,0 +1,27 @@ +/* dbg_x25519.c —— X25519 RFC 7748 迭代向量验证 */ +#include "paze/curve25519.h" +#include +#include + +int main(void) { + /* RFC 7748 §5.2 迭代向量: 1 的标量倍 = 0x2f e5 7d a3 ... */ + uint8_t k[32] = {1}, u[32] = {9}, out[32]; + if (paze_x25519(out, k, u) != PAZE_OK) { printf("x25519 fail\n"); return 1; } + static const uint8_t e1[32] = { + 0x2f,0xe5,0x7d,0xa3,0xc4,0x67,0x9c,0x1a,0x7d,0xeb,0x48,0x2a,0x0e,0xbd,0x59,0x6a, + 0x66,0x41,0xe6,0x1c,0xc4,0xd6,0x75,0x5f,0x35,0x6e,0xbe,0x6a,0xf2,0x2b,0x8d,0x7c + }; + printf("X25519(1,9): %s\n", memcmp(out, e1, 32) == 0 ? "PASS" : "FAIL"); + + /* 1000 次迭代 */ + uint8_t kk[32] = {9}; + for (int i = 0; i < 1000; i++) { + paze_x25519(kk, kk, u); + } + static const uint8_t e2[32] = { + 0x68,0x4c,0xf5,0x9b,0xa8,0x33,0x09,0x55,0x28,0x00,0xef,0x56,0x6f,0x2f,0x4d,0x3c, + 0x1c,0x38,0x87,0xc4,0x93,0x60,0xe3,0x87,0x5f,0x2e,0xb9,0x4a,0xe9,0x5a,0xc4,0x4f + }; + printf("X25519 1000-iter: %s\n", memcmp(kk, e2, 32) == 0 ? "PASS" : "FAIL"); + return 0; +} diff --git a/tests/verify_tls.c b/tests/verify_tls.c new file mode 100644 index 0000000..0e4a3ce --- /dev/null +++ b/tests/verify_tls.c @@ -0,0 +1,80 @@ +/* verify_tls.c —— TLS 相关密码原语验证 + * 测试 1: X25519 RFC 7748 固定向量 + * 测试 2: X.509 ECDSA P-256 自签证书 (make_self_signed -> parse -> verify) */ +#include "paze/curve25519.h" +#include "paze/x509.h" +#include "paze/ecdsa.h" +#include "paze/random.h" +#include "paze/error.h" +#include +#include + +static int hex_byte(char c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static int hex_decode(uint8_t *out, size_t outlen, const char *hex) { + size_t n = strlen(hex); + if (n != outlen * 2) return -1; + for (size_t i = 0; i < outlen; i++) { + int hi = hex_byte(hex[i * 2]); + int lo = hex_byte(hex[i * 2 + 1]); + if (hi < 0 || lo < 0) return -1; + out[i] = (uint8_t)((hi << 4) | lo); + } + return 0; +} + +/* RFC 7748 §5.2: X25519(Alice scalar, Bob u) == 共享密钥 */ +static int test_x25519(void) { + uint8_t scalar[32], u[32], out[32]; + if (hex_decode(scalar, 32, + "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a") != 0) return -1; + if (hex_decode(u, 32, + "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f") != 0) return -1; + if (paze_x25519(out, scalar, u) != PAZE_OK) return -1; + /* 期望共享密钥 */ + static const uint8_t expect[32] = { + 0x4a,0x5d,0x9d,0x5b,0xa4,0xce,0x2d,0xe1,0x72,0x8e,0x3b,0xf4,0x80,0x35,0x0f,0x25, + 0xe0,0x7e,0x21,0xc9,0x47,0xd1,0x9e,0x33,0x76,0xf0,0x9b,0x3c,0x1e,0x16,0x17,0x42 + }; + return memcmp(out, expect, 32) == 0; +} + +static int test_x509_ecdsa(void) { + uint8_t der[4096]; + size_t derlen = sizeof(der); + paze_x509_signer_t signer; + memset(&signer, 0, sizeof(signer)); + signer.kind = PAZE_X509_PK_ECDSA_P256; + paze_ecdsa_priv_t ec; + if (paze_ecdsa_gen(&ec) != PAZE_OK) return -10; + memcpy(signer.ecdsa_d, ec.d, 32); + + if (paze_x509_make_self_signed(der, &derlen, &signer, "test.example", + 1700000000LL, 1900000000LL) != PAZE_OK) return -11; + + paze_x509_cert_t cert; + memset(&cert, 0, sizeof(cert)); + if (paze_x509_parse_der(&cert, der, derlen) != PAZE_OK) return -12; + if (!paze_x509_is_self_signed(&cert)) return -13; + if (paze_x509_verify_signature(&cert, &cert) != PAZE_OK) return -14; + if (paze_x509_check_validity(&cert, 1750000000LL) != PAZE_OK) return -15; + if (!paze_x509_match_host(&cert, "test.example")) return -16; + return 0; +} + +int main(void) { + int r; + r = test_x25519(); + printf("[%s] X25519 RFC7748 vector\n", r == 0 ? "PASS" : "FAIL"); + if (r != 0) return 1; + r = test_x509_ecdsa(); + printf("[%s] X.509 ECDSA self-signed cert (rc=%d)\n", r == 0 ? "PASS" : "FAIL", r); + if (r != 0) return 1; + printf("all passed\n"); + return 0; +} diff --git a/up.bin b/up.bin new file mode 100644 index 0000000..01b6441 --- /dev/null +++ b/up.bin @@ -0,0 +1,2 @@ +hello-psftp +