实现 TLS 1.3 PSK 会话恢复,构建输出移至 bin/ 并新增 standalone/psftp 命令
TLS 1.3 PSK 会话恢复: NewSessionTicket 签发/解析、ticket+binder 校验、selected_identity 回选、恢复握手免证书(pazessl -sess_in/-sess_out 端到端验证); 构建脚本输出 bin/ 并支持独立命令与 psftp; 修复 SFTP 二进制传输与 copy_id -P; 补充 tests/ 调试与 verify_tls
这个提交包含在:
@@ -38,3 +38,5 @@ cmake --build build -j
|
|||||||
## 状态
|
## 状态
|
||||||
|
|
||||||
工程量巨大,按层推进。当前完成度见 `STATUS.md`。
|
工程量巨大,按层推进。当前完成度见 `STATUS.md`。
|
||||||
|
|
||||||
|
TLS 1.3 已覆盖:完整握手(ECDHE + X25519/P-256)、1.3↔1.2 版本回退、**PSK 会话恢复**(NewSessionTicket 签发/解析、binder 校验、恢复握手免证书),并通过 OpenSSL `s_server`/`s_client` 双向互操作对测。
|
||||||
|
|||||||
@@ -24,11 +24,12 @@
|
|||||||
## PazeSSL — TLS(🔶 部分完成)
|
## PazeSSL — TLS(🔶 部分完成)
|
||||||
|
|
||||||
- ✅ TLS 1.3 客户端/服务端状态机(record、handshake、key schedule、扩展)
|
- ✅ 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)
|
- ✅ 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`)
|
- ✅ OpenSSL 互操作对测 4/4:s_server↔s_client 双向 TLS 1.3 / TLS 1.2(`build/run_openssl_interop.ps1`)
|
||||||
- ✅ 证书链与信任锚加载、RSA/ECDSA 私钥 PEM 解析
|
- ✅ 证书链与信任锚加载、RSA/ECDSA 私钥 PEM 解析
|
||||||
- ⬜ TLS 1.3 服务端 SHA-384 密码套件握手重放(SHA-256 已覆盖)
|
- ⬜ TLS 1.3 服务端 SHA-384 密码套件握手重放(SHA-256 已覆盖)
|
||||||
- ⬜ PSK / 0-RTT / HRR 实际数据路径
|
- ⬜ 0-RTT / HRR 数据路径
|
||||||
|
|
||||||
## PazeSSH — SSH-2(✅ 完成)
|
## PazeSSH — SSH-2(✅ 完成)
|
||||||
|
|
||||||
@@ -68,4 +69,4 @@
|
|||||||
## 遗留项(已知)
|
## 遗留项(已知)
|
||||||
|
|
||||||
- X.509 ECDSA 证书解析仍失败(P-256 曲线点验证)
|
- X.509 ECDSA 证书解析仍失败(P-256 曲线点验证)
|
||||||
- TLS 1.3 SHA-384 服务端握手重放、PSK/0-RTT 未覆盖
|
- TLS 1.3 SHA-384 服务端握手重放、0-RTT / HRR 未覆盖
|
||||||
|
|||||||
+2
-2
@@ -103,7 +103,7 @@ int pssh_cmd_copyid(int argc, char **argv) {
|
|||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if ((strcmp(argv[i], "-i") == 0) && i + 1 < argc) keyfile = argv[++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 (strcmp(argv[i], "-pwd") == 0 && i + 1 < argc) password_arg = argv[++i];
|
||||||
else if (argv[i][0] == '-') continue;
|
else if (argv[i][0] == '-') continue;
|
||||||
else {
|
else {
|
||||||
@@ -118,7 +118,7 @@ int pssh_cmd_copyid(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!host) {
|
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;
|
return 1;
|
||||||
}
|
}
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
|||||||
+90
-33
@@ -97,6 +97,22 @@ static uint8_t *read_file(const char *path, size_t *out_len) {
|
|||||||
return buf;
|
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) {
|
int main(int argc, char **argv) {
|
||||||
if (argc < 2) return usage();
|
if (argc < 2) return usage();
|
||||||
const char *cmd = argv[1];
|
const char *cmd = argv[1];
|
||||||
@@ -106,6 +122,8 @@ int main(int argc, char **argv) {
|
|||||||
const char *host = NULL; int port = 443;
|
const char *host = NULL; int port = 443;
|
||||||
const char *cafile = NULL;
|
const char *cafile = NULL;
|
||||||
const char *sni = NULL;
|
const char *sni = NULL;
|
||||||
|
const char *sess_in = NULL, *sess_out = NULL;
|
||||||
|
const char *msg = NULL;
|
||||||
int only_12 = 0;
|
int only_12 = 0;
|
||||||
for (int i = 2; i < argc; i++) {
|
for (int i = 2; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "-connect") == 0 && i + 1 < argc) {
|
if (strcmp(argv[i], "-connect") == 0 && i + 1 < argc) {
|
||||||
@@ -117,6 +135,12 @@ int main(int argc, char **argv) {
|
|||||||
cafile = argv[++i];
|
cafile = argv[++i];
|
||||||
} else if (strcmp(argv[i], "-servername") == 0 && i + 1 < argc) {
|
} else if (strcmp(argv[i], "-servername") == 0 && i + 1 < argc) {
|
||||||
sni = argv[++i];
|
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) {
|
} else if (strcmp(argv[i], "-tls1_2") == 0) {
|
||||||
only_12 = 1;
|
only_12 = 1;
|
||||||
}
|
}
|
||||||
@@ -136,6 +160,11 @@ int main(int argc, char **argv) {
|
|||||||
free(cdata);
|
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);
|
sock_t sock = tcp_connect(host, port);
|
||||||
if (sock == SOCK_INVALID) { fprintf(stderr, "connect failed\n"); return 1; }
|
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);
|
paze_tls_free(tls); closesocket(sock); paze_tls_config_free(cfg);
|
||||||
return 1;
|
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_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];
|
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));
|
int n = paze_tls_read(tls, buf, sizeof(buf));
|
||||||
if (n > 0) fwrite(buf, 1, (size_t)n, stdout);
|
if (n > 0) { fwrite(buf, 1, (size_t)n, stdout); fprintf(stderr, "\n"); }
|
||||||
if (n <= 0) break;
|
} 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_close(tls);
|
||||||
paze_tls_free(tls);
|
paze_tls_free(tls);
|
||||||
@@ -303,10 +348,12 @@ int main(int argc, char **argv) {
|
|||||||
int port = 4433;
|
int port = 4433;
|
||||||
const char *certf = NULL; const char *keyf = NULL;
|
const char *certf = NULL; const char *keyf = NULL;
|
||||||
int only_12 = 0;
|
int only_12 = 0;
|
||||||
|
int reuse = 1;
|
||||||
for (int i = 2; i < argc; i++) {
|
for (int i = 2; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "-accept") == 0 && i + 1 < argc) port = atoi(argv[++i]);
|
if (strcmp(argv[i], "-accept") == 0 && i + 1 < argc) port = atoi(argv[++i]);
|
||||||
else if (strcmp(argv[i], "-cert") == 0 && i + 1 < argc) certf = 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], "-key") == 0 && i + 1 < argc) keyf = argv[++i];
|
||||||
|
else if (strcmp(argv[i], "-reuse") == 0 && i + 1 < argc) reuse = atoi(argv[++i]);
|
||||||
else if (strcmp(argv[i], "-tls1_2") == 0) only_12 = 1;
|
else if (strcmp(argv[i], "-tls1_2") == 0) only_12 = 1;
|
||||||
}
|
}
|
||||||
if (!certf || !keyf) {
|
if (!certf || !keyf) {
|
||||||
@@ -344,44 +391,54 @@ int main(int argc, char **argv) {
|
|||||||
fprintf(stderr, "bind port %d failed\n", port);
|
fprintf(stderr, "bind port %d failed\n", port);
|
||||||
closesocket(lsock); return 1;
|
closesocket(lsock); return 1;
|
||||||
}
|
}
|
||||||
if (listen(lsock, 1) < 0) {
|
if (listen(lsock, 4) < 0) {
|
||||||
fprintf(stderr, "listen failed\n");
|
fprintf(stderr, "listen failed\n");
|
||||||
closesocket(lsock); return 1;
|
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;
|
int overall = 0;
|
||||||
socklen_t cl = sizeof(client);
|
for (int conn_idx = 0; conn_idx < reuse; conn_idx++) {
|
||||||
sock_t conn = accept(lsock, (struct sockaddr *)&client, &cl);
|
struct sockaddr_in client;
|
||||||
if (conn == SOCK_INVALID) { closesocket(lsock); return 1; }
|
socklen_t cl = sizeof(client);
|
||||||
fprintf(stderr, "Client connected\n");
|
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_t *tls = paze_tls_new(cfg);
|
||||||
paze_tls_bio_t bio = { &conn, sock_read, sock_write };
|
paze_tls_bio_t bio = { &conn, sock_read, sock_write };
|
||||||
paze_tls_set_bio(tls, bio);
|
paze_tls_set_bio(tls, bio);
|
||||||
|
|
||||||
paze_status_t st = paze_tls_handshake(tls);
|
paze_status_t st = paze_tls_handshake(tls);
|
||||||
if (st != PAZE_OK) {
|
if (st != PAZE_OK) {
|
||||||
fprintf(stderr, "TLS handshake failed: %s\n", paze_tls_last_error(tls));
|
fprintf(stderr, "TLS handshake failed: %s\n", paze_tls_last_error(tls));
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "TLS handshake OK (version %04x, cipher %04x)\n",
|
fprintf(stderr, "TLS handshake OK (version %04x, cipher %04x, resumed %s)\n",
|
||||||
(unsigned)paze_tls_get_version(tls),
|
(unsigned)paze_tls_get_version(tls),
|
||||||
(unsigned)paze_tls_get_cipher_suite(tls));
|
(unsigned)paze_tls_get_cipher_suite(tls),
|
||||||
/* Echo loop */
|
paze_tls_session_resumed(tls) ? "YES" : "no");
|
||||||
uint8_t buf[4096];
|
/* 跨连接共享 session ticket key,使后续连接可恢复会话 */
|
||||||
for (int i = 0; i < 100; i++) {
|
uint8_t tk[32]; int tk_ready = 0;
|
||||||
int n = paze_tls_read(tls, buf, sizeof(buf));
|
paze_tls_get_session_ticket_key(tls, tk, &tk_ready);
|
||||||
if (n <= 0) break;
|
if (tk_ready) paze_tls_config_set_session_ticket_key(cfg, tk);
|
||||||
paze_tls_write(tls, buf, (size_t)n);
|
/* 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);
|
closesocket(lsock);
|
||||||
paze_tls_config_free(cfg);
|
paze_tls_config_free(cfg);
|
||||||
free(cdata); free(kdata);
|
free(cdata); free(kdata);
|
||||||
return st == PAZE_OK ? 0 : 1;
|
return overall;
|
||||||
}
|
}
|
||||||
|
|
||||||
fprintf(stderr, "pazessl %s: not yet implemented\n", cmd);
|
fprintf(stderr, "pazessl %s: not yet implemented\n", cmd);
|
||||||
|
|||||||
+709
@@ -0,0 +1,709 @@
|
|||||||
|
/* psftp —— 独立 SFTP 客户端 (平替 OpenSSH sftp)
|
||||||
|
*
|
||||||
|
* 用法:
|
||||||
|
* psftp [选项] [user@]host[:初始路径]
|
||||||
|
* -P <port> 远程端口 (默认 22)
|
||||||
|
* -pwd <pw> 密码
|
||||||
|
* -i <key> 私钥文件 (publickey 认证)
|
||||||
|
* -o <opt> StrictHostKeyChecking=no|accept-new|yes
|
||||||
|
* -b <file> 批处理:逐行执行命令后退出
|
||||||
|
*
|
||||||
|
* 交互命令:
|
||||||
|
* bye/exit/quit 退出
|
||||||
|
* pwd/lpwd 显示远端/本地当前目录
|
||||||
|
* cd/lcd <path> 切换远端/本地目录
|
||||||
|
* ls [path] 列出远端目录
|
||||||
|
* lls [path] 列出本地目录
|
||||||
|
* get [-r] <remote> [local] 下载(文件或目录)
|
||||||
|
* put [-r] <local> [remote] 上传(文件或目录)
|
||||||
|
* rm <path> 删除远端文件
|
||||||
|
* mkdir <path> 创建远端目录
|
||||||
|
* rmdir <path> 删除远端空目录
|
||||||
|
* rename <old> <new> 远端重命名
|
||||||
|
* help/?
|
||||||
|
*/
|
||||||
|
#include "paze/ssh.h"
|
||||||
|
#include "paze/ssh_agent.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#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 <windows.h>
|
||||||
|
#include <conio.h>
|
||||||
|
#include <direct.h>
|
||||||
|
#define GETCH _getch
|
||||||
|
#define IS_SEP(c) ((c) == '/' || (c) == '\\')
|
||||||
|
static int local_mkdir(const char *p) { return _mkdir(p); }
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#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 <path> 切换远端 / 本地目录\n"
|
||||||
|
" ls [path] 列出远端目录\n"
|
||||||
|
" lls [path] 列出本地目录\n"
|
||||||
|
" get [-r] <remote> [local] 下载(文件或目录)\n"
|
||||||
|
" put [-r] <local> [remote] 上传(文件或目录)\n"
|
||||||
|
" rm <path> 删除远端文件\n"
|
||||||
|
" mkdir <path> 创建远端目录\n"
|
||||||
|
" rmdir <path> 删除远端空目录\n"
|
||||||
|
" rename <old> <new> 远端重命名\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 <port> 远程端口 (默认 22)\n"
|
||||||
|
" -pwd <pw> 密码\n"
|
||||||
|
" -i <key> 私钥文件 (publickey 认证)\n"
|
||||||
|
" -o <opt> StrictHostKeyChecking=no|accept-new|yes\n"
|
||||||
|
" -b <file> 批处理模式:逐行执行命令后退出\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;
|
||||||
|
}
|
||||||
@@ -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 <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
# build.ps1 —— 无 CMake 环境下的源码构建脚本
|
# build.ps1 —— 无 CMake 环境下的源码构建脚本
|
||||||
# 编译三个静态库(paze_crypto/paze_ssl/paze_ssh)与 CLI 应用(pssh/pazessl)
|
# 编译三个静态库(paze_crypto/paze_ssl/paze_ssh)与 CLI 应用(pssh/pazessl)
|
||||||
|
# 发布 exe 全部输出到 bin/ 文件夹,中间产物(obj/静态库)留在 build/
|
||||||
# 用法: .\build.ps1 [-Clean] [-Debug]
|
# 用法: .\build.ps1 [-Clean] [-Debug]
|
||||||
param(
|
param(
|
||||||
[switch]$Clean,
|
[switch]$Clean,
|
||||||
@@ -9,10 +10,11 @@ param(
|
|||||||
$ErrorActionPreference = "Continue"
|
$ErrorActionPreference = "Continue"
|
||||||
$Root = $PSScriptRoot
|
$Root = $PSScriptRoot
|
||||||
$Build = Join-Path $Root "build"
|
$Build = Join-Path $Root "build"
|
||||||
|
$Bin = Join-Path $Root "bin"
|
||||||
$Obj = Join-Path $Build "obj"
|
$Obj = Join-Path $Build "obj"
|
||||||
|
|
||||||
if ($Clean -and (Test-Path $Build)) { Remove-Item -Recurse -Force $Build }
|
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"
|
$cc = "gcc"
|
||||||
$opt = if ($Debug) { "-O0", "-g" } else { "-O2" }
|
$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) {
|
function Link-App($Main, $Libs, $Out) {
|
||||||
Write-Host "== $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" }
|
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")
|
& $cc @cflags -c $psshMain -o (Join-Path $Obj "app_pssh_main.o")
|
||||||
if ($LASTEXITCODE -ne 0) { throw "compile pssh main failed" }
|
if ($LASTEXITCODE -ne 0) { throw "compile pssh main failed" }
|
||||||
$appObjs = @()
|
$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"
|
$am = Join-Path $ad.FullName "main.c"
|
||||||
if (-not (Test-Path $am)) { continue }
|
if (-not (Test-Path $am)) { continue }
|
||||||
$o = Join-Path $Obj ("app_" + $ad.Name + ".o")
|
$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 += (Join-Path $Obj "app_pssh_main.o")
|
||||||
$appObjs = $appObjs | ForEach-Object { $_.Replace("\", "/") }
|
$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" }
|
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"
|
Link-App (Join-Path $Root "apps\pazessl\main.c") @($sslA, $cryptoA) "pazessl.exe"
|
||||||
|
|
||||||
Write-Host "== done =="
|
Write-Host "== done =="
|
||||||
|
|||||||
+36
-1
@@ -1,7 +1,7 @@
|
|||||||
# TLS 1.2/1.3 自测脚本(一次性)
|
# TLS 1.2/1.3 自测脚本(一次性)
|
||||||
$ErrorActionPreference = "Continue"
|
$ErrorActionPreference = "Continue"
|
||||||
$Root = "F:\TLS-SSH"
|
$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"
|
$key = Join-Path $Root "build\key.pem"
|
||||||
$cert = Join-Path $Root "build\cert.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
|
# 回退场景 B: 客户端仅 1.2 ↔ 服务端默认(先试 1.3) → 服务端回退到 1.2
|
||||||
Test-Conn "fallback-srv" "14436" @("s_server","-accept","14436","-cert",$cert,"-key",$key) `
|
Test-Conn "fallback-srv" "14436" @("s_server","-accept","14436","-cert",$cert,"-key",$key) `
|
||||||
@("s_client","-connect","127.0.0.1:14436","-tls1_2")
|
@("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
|
||||||
|
|||||||
@@ -171,6 +171,24 @@ typedef enum {
|
|||||||
PAZE_TLS_VERIFY_REQUIRED = 2 /* 默认:服务端必须可信 */
|
PAZE_TLS_VERIFY_REQUIRED = 2 /* 默认:服务端必须可信 */
|
||||||
} paze_tls_verify_mode_t;
|
} 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);
|
paze_tls_config_t *paze_tls_config_new(paze_tls_role_t role);
|
||||||
void paze_tls_config_free(paze_tls_config_t *cfg);
|
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;
|
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);
|
paze_tls_t *paze_tls_new(const paze_tls_config_t *cfg);
|
||||||
void paze_tls_free(paze_tls_t *tls);
|
void paze_tls_free(paze_tls_t *tls);
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -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];
|
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(&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;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_TRUNC) of |= O_TRUNC;
|
||||||
if (pflags & SSH_FXF_EXCL) of |= O_EXCL;
|
if (pflags & SSH_FXF_EXCL) of |= O_EXCL;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
of |= O_BINARY; /* 二进制模式:否则 _write 会把 \n 转成 \r\n,损坏上传内容 */
|
||||||
wchar_t w[2048];
|
wchar_t w[2048];
|
||||||
if (utf8_to_wide(path, w, 2048) < 0) return -1;
|
if (utf8_to_wide(path, w, 2048) < 0) return -1;
|
||||||
return _wopen(w, of, _S_IREAD | _S_IWRITE);
|
return _wopen(w, of, _S_IREAD | _S_IWRITE);
|
||||||
|
|||||||
@@ -213,6 +213,63 @@ paze_status_t paze_tls_config_set_versions(paze_tls_config_t *cfg,
|
|||||||
return PAZE_OK;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================================
|
/* ============================================================
|
||||||
* 连接对象
|
* 连接对象
|
||||||
* ============================================================ */
|
* ============================================================ */
|
||||||
|
|||||||
@@ -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;
|
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。
|
* 对 extensions 块(含外层 2 字节 ext_total_len)逐条调用 cb。
|
||||||
|
|||||||
+332
-25
@@ -1,8 +1,10 @@
|
|||||||
/* tls_handshake13.c —— TLS 1.3 (RFC 8446) 完整握手状态机
|
/* tls_handshake13.c —— TLS 1.3 (RFC 8446) 完整握手状态机
|
||||||
*
|
*
|
||||||
* 覆盖的握手流程(无 PSK, 无 0-RTT):
|
* 覆盖的握手流程(无 0-RTT):
|
||||||
* Client: ClientHello → 收 ServerHello → 收 EncryptedExtensions → 收 Certificate/CertificateVerify/Finished → 发 Finished
|
* Client: ClientHello → 收 ServerHello → 收 EncryptedExtensions → 收 Certificate/CertificateVerify/Finished → 发 Finished
|
||||||
* Server: 收 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 回调函数。
|
* 所有 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_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_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;
|
uint8_t *p = out;
|
||||||
p += 4;
|
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_alpn(p, cfg->alpn, cfg->alpn_count); p += n;
|
||||||
n = paze_tls_ext_write_renegotiation_info(p); p += n;
|
n = paze_tls_ext_write_renegotiation_info(p); p += n;
|
||||||
|
|
||||||
|
/* 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));
|
size_t ext_total = (size_t)(p - (ext_total_len_ptr + 2));
|
||||||
paze_store16_be(ext_total_len_ptr, (uint16_t)ext_total);
|
paze_store16_be(ext_total_len_ptr, (uint16_t)ext_total);
|
||||||
|
|
||||||
@@ -284,6 +338,8 @@ typedef struct {
|
|||||||
uint16_t *g;
|
uint16_t *g;
|
||||||
const uint8_t **pub;
|
const uint8_t **pub;
|
||||||
size_t *plen;
|
size_t *plen;
|
||||||
|
uint16_t *sel_psk; /* ServerHello pre_shared_key selected_identity */
|
||||||
|
int *fpsk; /* ServerHello 含 pre_shared_key */
|
||||||
} sh_ext_ctx_t;
|
} sh_ext_ctx_t;
|
||||||
|
|
||||||
static int sh_ext_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) {
|
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) {
|
} else if (tp == PAZE_TLS_EXT_KEY_SHARE) {
|
||||||
*c->g = paze_tls_ext_parse_key_share_server(b, l, c->pub, c->plen);
|
*c->g = paze_tls_ext_parse_key_share_server(b, l, c->pub, c->plen);
|
||||||
if (*c->g) *c->fks = 1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -347,6 +405,43 @@ static int ks_lookup_cb(uint16_t tp, const uint8_t *b, size_t l, void *x) {
|
|||||||
/* ============================================================
|
/* ============================================================
|
||||||
* [D] 客户端握手
|
* [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_status_t paze_tls13_client_handshake(paze_tls_t *tls) {
|
||||||
paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg;
|
paze_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg;
|
||||||
paze_status_t st;
|
paze_status_t st;
|
||||||
@@ -356,6 +451,19 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) {
|
|||||||
if (st != PAZE_OK) return st;
|
if (st != PAZE_OK) return st;
|
||||||
paze_tls_tr_init(&tls->transcript, paze_hash_sha256());
|
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;
|
uint8_t ch[PAZE_TLS_MAX_RECORD]; size_t ch_len = 0;
|
||||||
st = paze_tls13_build_ch(tls, ch, &ch_len);
|
st = paze_tls13_build_ch(tls, ch, &ch_len);
|
||||||
if (st != PAZE_OK) return st;
|
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;
|
int found_v = 0, found_ks = 0;
|
||||||
uint16_t group = 0;
|
uint16_t group = 0;
|
||||||
const uint8_t *ks_pub = NULL; size_t ks_publen = 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);
|
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;
|
tls->neg.group = group;
|
||||||
|
|
||||||
uint8_t shared[64]; size_t shared_len = 0;
|
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 (found_ks) {
|
||||||
if (st != PAZE_OK) return st;
|
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);
|
st = paze_tls13_derive_handshake_keys(tls, shared, shared_len);
|
||||||
paze_memzero(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->write_key = &tls->app_write;
|
||||||
tls->handshake_done = 1;
|
tls->handshake_done = 1;
|
||||||
tls->hs_state = PAZE_HS_CL_DONE;
|
tls->hs_state = PAZE_HS_CL_DONE;
|
||||||
|
|
||||||
|
/* 读取服务端的 NewSessionTicket(post-handshake,用 app keys 加密) */
|
||||||
|
client_recv_new_session_ticket(tls);
|
||||||
}
|
}
|
||||||
return PAZE_OK;
|
return PAZE_OK;
|
||||||
}
|
}
|
||||||
@@ -575,6 +703,154 @@ paze_status_t paze_tls13_client_handshake(paze_tls_t *tls) {
|
|||||||
/* ============================================================
|
/* ============================================================
|
||||||
* [E] 服务端握手
|
* [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_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_tls_config_t *cfg = tls->cfg_owned ? tls->cfg_owned : (paze_tls_config_t *)tls->cfg;
|
||||||
paze_status_t st;
|
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;
|
if (!sx.g) return PAZE_ERR_PROTOCOL;
|
||||||
tls->neg.group = sx.g;
|
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];
|
char sni_buf[PAZE_TLS_SNI_MAX + 1];
|
||||||
sni_buf[0] = 0;
|
sni_buf[0] = 0;
|
||||||
if (sx.s) {
|
if (sx.s) {
|
||||||
@@ -653,17 +936,19 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) {
|
|||||||
int have_share = 0;
|
int have_share = 0;
|
||||||
ks_lookup_ctx_t ksl = { select_group, &ks_pub, &ks_publen, &have_share };
|
ks_lookup_ctx_t ksl = { select_group, &ks_pub, &ks_publen, &have_share };
|
||||||
paze_tls_extensions_foreach(exts, 2 + ext_total, ks_lookup_cb, &ksl);
|
paze_tls_extensions_foreach(exts, 2 + ext_total, ks_lookup_cb, &ksl);
|
||||||
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);
|
paze_tls_set_err(tls, "CH missing key share for group %d", select_group);
|
||||||
return PAZE_ERR_PROTOCOL;
|
return PAZE_ERR_PROTOCOL;
|
||||||
}
|
}
|
||||||
|
/* psk_ke:shared_len 保持 0,handshake_secret IKM 用 0 */
|
||||||
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;
|
|
||||||
|
|
||||||
/* Build ServerHello */
|
/* Build ServerHello */
|
||||||
uint8_t sh[PAZE_TLS_MAX_RECORD];
|
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;
|
uint8_t *extp = sp; sp += 2;
|
||||||
size_t n;
|
size_t n;
|
||||||
n = paze_tls_ext_write_supported_versions(sp, 0, 1, 0); sp += n;
|
n = paze_tls_ext_write_supported_versions(sp, 0, 1, 0); sp += n;
|
||||||
if (select_group == PAZE_TLS_GROUP_X25519)
|
if (have_share) {
|
||||||
n = paze_tls_ext_write_key_share_server(sp, PAZE_TLS_GROUP_X25519, tls->kex.x25519_pub, 32);
|
if (select_group == PAZE_TLS_GROUP_X25519)
|
||||||
else
|
n = paze_tls_ext_write_key_share_server(sp, PAZE_TLS_GROUP_X25519, tls->kex.x25519_pub, 32);
|
||||||
n = paze_tls_ext_write_key_share_server(sp, PAZE_TLS_GROUP_SECP256R1, tls->kex.p256_pub, 65);
|
else
|
||||||
sp += n;
|
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));
|
paze_store16_be(extp, (uint16_t)(sp - extp - 2));
|
||||||
size_t sh_body_len = (size_t)(sp - (sh + 4));
|
size_t sh_body_len = (size_t)(sp - (sh + 4));
|
||||||
paze_tls_hs_put_header(sh, PAZE_TLS_HS_SERVER_HELLO, sh_body_len);
|
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);
|
st = send_hs(tls, PAZE_TLS_HS_ENCRYPTED_EXTENSIONS, ee + 4, body_len);
|
||||||
if (st != PAZE_OK) return st;
|
if (st != PAZE_OK) return st;
|
||||||
|
|
||||||
/* Certificate:cfg->cert_der 是链式 blob([3字节长][DER] 逐条),
|
/* Certificate 与 CertificateVerify:PSK 恢复握手不发证书
|
||||||
* 需转成 RFC 8446 certificate_list(每条目追加 2 字节空 extensions)。 */
|
* (RFC 8446 §4.2.11),仅全握手发送。 */
|
||||||
if (cfg->cert_der == NULL || cfg->cert_der_len == 0) {
|
if (!tls->psk_mode) {
|
||||||
paze_tls_set_err(tls, "no server certificate configured");
|
/* Certificate:cfg->cert_der 是链式 blob([3字节长][DER] 逐条),
|
||||||
return PAZE_ERR_INVALID;
|
* 需转成 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 cert_body[8192];
|
||||||
uint8_t *cp = cert_body;
|
uint8_t *cp = cert_body;
|
||||||
*cp++ = 0x00; /* certificate_request_context 长度 */
|
*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);
|
paze_store16_be(cv + 2, (uint16_t)sig_len);
|
||||||
st = send_hs(tls, PAZE_TLS_HS_CERTIFICATE_VERIFY, cv, 4 + sig_len);
|
st = send_hs(tls, PAZE_TLS_HS_CERTIFICATE_VERIFY, cv, 4 + sig_len);
|
||||||
if (st != PAZE_OK) return st;
|
if (st != PAZE_OK) return st;
|
||||||
|
} /* !psk_mode */
|
||||||
|
|
||||||
/* Finished */
|
/* Finished */
|
||||||
uint8_t vd[64]; size_t vd_len = 0;
|
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) {
|
while (tls->hs_state == PAZE_HS_SV_WAIT_FINISHED) {
|
||||||
const uint8_t *body; size_t blen; uint8_t type;
|
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 (st != PAZE_OK) return st;
|
||||||
if (type == PAZE_TLS_HS_CERTIFICATE) continue;
|
if (type == PAZE_TLS_HS_CERTIFICATE) continue;
|
||||||
if (type == PAZE_TLS_HS_CERTIFICATE_VERIFY) continue;
|
if (type == PAZE_TLS_HS_CERTIFICATE_VERIFY) continue;
|
||||||
@@ -790,11 +1085,23 @@ paze_status_t paze_tls13_server_handshake(paze_tls_t *tls) {
|
|||||||
st = verify_finished(tls, 0, body, blen);
|
st = verify_finished(tls, 0, body, blen);
|
||||||
if (st != PAZE_OK) { paze_tls_set_err(tls, "client Finished verify failed"); return PAZE_ERR_VERIFY; }
|
if (st != PAZE_OK) { paze_tls_set_err(tls, "client Finished verify failed"); return PAZE_ERR_VERIFY; }
|
||||||
|
|
||||||
|
/* 先派生 app keys:其 transcript 为 CH...server Finished(不含 client Finished)。
|
||||||
|
* 再把 client Finished 计入 transcript,供 resumption_master_secret 使用。 */
|
||||||
st = paze_tls13_derive_app_keys(tls);
|
st = paze_tls13_derive_app_keys(tls);
|
||||||
if (st != PAZE_OK) return st;
|
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->read_key = &tls->app_read;
|
||||||
tls->write_key = &tls->app_write;
|
tls->write_key = &tls->app_write;
|
||||||
tls->handshake_done = 1;
|
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;
|
tls->hs_state = PAZE_HS_SV_DONE;
|
||||||
}
|
}
|
||||||
return PAZE_OK;
|
return PAZE_OK;
|
||||||
|
|||||||
@@ -120,6 +120,15 @@ struct paze_tls_config {
|
|||||||
|
|
||||||
int enable_1_3;
|
int enable_1_3;
|
||||||
int enable_1_2;
|
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 client_finished_key[64];
|
||||||
uint8_t server_finished_key[64];
|
uint8_t server_finished_key[64];
|
||||||
uint8_t binder_key[64];
|
uint8_t binder_key[64];
|
||||||
|
uint8_t 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 */
|
/* transcript */
|
||||||
paze_tls_transcript_t 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,
|
const uint8_t *base_key, size_t bklen,
|
||||||
uint8_t *out_verify_data);
|
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) */
|
/* TLS 1.2 PRF (HMAC-based) */
|
||||||
paze_status_t paze_tls12_prf(const paze_hash_alg_t *alg,
|
paze_status_t paze_tls12_prf(const paze_hash_alg_t *alg,
|
||||||
const uint8_t *secret, size_t slen,
|
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) */
|
/* 服务端:构造 key_share 扩展(单 group) */
|
||||||
size_t paze_tls_ext_write_key_share_server(uint8_t *buf, uint16_t group,
|
size_t paze_tls_ext_write_key_share_server(uint8_t *buf, uint16_t group,
|
||||||
const uint8_t *pub, size_t publen);
|
const uint8_t *pub, size_t publen);
|
||||||
|
/* 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 解析 ---- */
|
/* ---- extensions 解析 ---- */
|
||||||
int paze_tls_ext_parse_supported_versions(const uint8_t *data, size_t len, int *picked);
|
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]);
|
char out[PAZE_TLS_SNI_MAX + 1]);
|
||||||
int paze_tls_ext_parse_alpn(const uint8_t *data, size_t len,
|
int paze_tls_ext_parse_alpn(const uint8_t *data, size_t len,
|
||||||
char out[PAZE_TLS_MAX_ALPN][256]);
|
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 paze_tls_extensions_foreach(const uint8_t *exts, size_t exts_len,
|
||||||
int (*cb)(uint16_t type, const uint8_t *body,
|
int (*cb)(uint16_t type, const uint8_t *body,
|
||||||
size_t blen, void *ctx),
|
size_t blen, void *ctx),
|
||||||
|
|||||||
+66
-4
@@ -142,15 +142,25 @@ paze_status_t paze_tls13_derive_handshake_keys(paze_tls_t *tls,
|
|||||||
size_t hl = H->digest_len;
|
size_t hl = H->digest_len;
|
||||||
uint8_t zero[64]; memset(zero, 0, sizeof(zero));
|
uint8_t zero[64]; memset(zero, 0, sizeof(zero));
|
||||||
|
|
||||||
/* early_secret = HKDF-Extract(0, 0) (no PSK) */
|
/* early_secret = HKDF-Extract(0, PSK) 或 HKDF-Extract(0, 0) */
|
||||||
paze_hkdf_extract(H, zero, hl, zero, hl, tls->early_secret);
|
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", "") */
|
/* derived = Derive-Secret(early_secret, "derived", "") */
|
||||||
uint8_t derived[64];
|
uint8_t derived[64];
|
||||||
paze_tls_derive_secret(H, tls->early_secret, hl, "derived", NULL, derived);
|
paze_tls_derive_secret(H, tls->early_secret, hl, "derived", NULL, derived);
|
||||||
|
|
||||||
/* handshake_secret = HKDF-Extract(derived, ECDHE) */
|
/* handshake_secret = HKDF-Extract(derived, ECDHE)。
|
||||||
paze_hkdf_extract(H, derived, hl, shared, shared_len, tls->handshake_secret);
|
* 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 */
|
/* client/server handshake traffic secrets */
|
||||||
uint8_t c_hs[64], s_hs[64];
|
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;
|
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 ---- */
|
/* ---- Finished verify_data ---- */
|
||||||
paze_status_t paze_tls13_compute_finished(const paze_tls_t *tls,
|
paze_status_t paze_tls13_compute_finished(const paze_tls_t *tls,
|
||||||
const uint8_t *finished_key, size_t fklen,
|
const uint8_t *finished_key, size_t fklen,
|
||||||
|
|||||||
@@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
/* dbg_div2.c —— divmod 小案例与中间量定位 */
|
||||||
|
#include "paze/p256.h"
|
||||||
|
#include "paze/bignum.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
/* dbg_div3.c —— divmod 恒等式自检: q*b + r == a 且 0 <= r < b */
|
||||||
|
#include "paze/p256.h"
|
||||||
|
#include "paze/bignum.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
/* dbg_div4.c —— 找出 divmod 最小失败案例 */
|
||||||
|
#include "paze/p256.h"
|
||||||
|
#include "paze/bignum.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
/* dbg_divmod.c —— divmod/mod 针对性测试 */
|
||||||
|
#include "paze/p256.h"
|
||||||
|
#include "paze/bignum.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/* dbg_ecdsa.c —— ECDSA sign/verify 单元定位 */
|
||||||
|
#include "paze/ecdsa.h"
|
||||||
|
#include "paze/bignum.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/* dbg_ecdsa_cert.c —— 定位 OpenSSL ECDSA 证书验签失败点 */
|
||||||
|
#include "paze/x509.h"
|
||||||
|
#include "paze/ecdsa.h"
|
||||||
|
#include "paze/hash.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
+86
@@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
/* dbg_p2.c —— 单测 p^2 / p 的 Knuth D 过程 */
|
||||||
|
#include "paze/bignum.h"
|
||||||
|
#include "paze/p256.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/* dbg_point.c —— P-256 点运算验证 (2G/3G 标准值) */
|
||||||
|
#include "paze/p256.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/* dbg_point2.c —— 深入定位 add/dbl 一致性
|
||||||
|
* 核心问题:G+G(add) != 2G(dbl), 且两者都声称 on curve */
|
||||||
|
#include "paze/p256.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/* dbg_x25519.c —— X25519 RFC 7748 迭代向量验证 */
|
||||||
|
#include "paze/curve25519.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
在新工单中引用
屏蔽一个用户