文件
PazeSSH/apps/pazessl/main.c
T
JGZYES 3e8a5442fa 实现 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
2026-08-13 19:30:07 +08:00

447 行
17 KiB
C

/* pazessl —— PazeSSL 命令行入口
*
* 子命令:
* s_client - TLS 客户端连接并交互
* s_server - TLS 服务端
* verify - 验证证书链
* genrsa - 生成 RSA 密钥
* req - 自签名证书
* x509 - 打印证书
*/
#include "paze/tls.h"
#include "paze/encoding.h"
#include "paze/rsa.h"
#include "paze/x509.h"
#include "paze/random.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#ifdef _WIN32
#include <winsock2.h>
#ifndef SHUT_RDWR
#define SHUT_RDWR SD_BOTH
#endif
typedef SOCKET sock_t;
typedef int socklen_t;
#define SOCK_INVALID INVALID_SOCKET
#else
#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
typedef int sock_t;
typedef socklen_t socklen_t;
#define SOCK_INVALID (-1)
#define closesocket close
#endif
static sock_t tcp_connect(const char *host, int port) {
#ifdef _WIN32
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#endif
struct hostent *he = gethostbyname(host);
if (!he) return SOCK_INVALID;
sock_t s = socket(AF_INET, SOCK_STREAM, 0);
if (s == SOCK_INVALID) return SOCK_INVALID;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
memcpy(&addr.sin_addr, he->h_addr_list[0], (size_t)he->h_length);
if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
closesocket(s); return SOCK_INVALID;
}
return s;
}
static int sock_read(void *ctx, uint8_t *buf, size_t n) {
sock_t s = *(sock_t *)ctx;
return (int)recv(s, (char *)buf, (int)n, 0);
}
static int sock_write(void *ctx, const uint8_t *buf, size_t n) {
sock_t s = *(sock_t *)ctx;
return (int)send(s, (char *)buf, (int)n, 0);
}
static int usage(void) {
fprintf(stderr,
"PazeSSL v0.1 (libc-only TLS 1.3 + 1.2)\n"
"Usage:\n"
" pazessl s_client -connect host:port [-CAfile roots.pem] [-servername SNI] [-tls1_2]\n"
" pazessl s_server -accept port -cert cert.pem -key key.pem [-tls1_2]\n"
" pazessl verify -CAfile roots.pem cert.pem\n"
" pazessl genrsa -out key.pem [2048]\n"
" pazessl req -x509 -newkey rsa:2048 -keyout k.pem -out c.pem -days D -subj /CN=X\n"
" pazessl x509 -in cert.pem [-text] [-noout]\n"
);
return 1;
}
/* Read entire file into malloc'd buffer */
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;
}
/* TLS 1.3 session 持久化(二进制 dump,自用格式) */
static int session_write_file(const char *path, const paze_tls_session_t *s) {
FILE *f = fopen(path, "wb");
if (!f) return -1;
fwrite(s, 1, sizeof(*s), f);
fclose(f);
return 0;
}
static int session_read_file(const char *path, paze_tls_session_t *s) {
FILE *f = fopen(path, "rb");
if (!f) return -1;
size_t n = fread(s, 1, sizeof(*s), f);
fclose(f);
return n == sizeof(*s) ? 0 : -1;
}
int main(int argc, char **argv) {
if (argc < 2) return usage();
const char *cmd = argv[1];
/* s_client */
if (strcmp(cmd, "s_client") == 0) {
const char *host = NULL; int port = 443;
const char *cafile = NULL;
const char *sni = NULL;
const char *sess_in = NULL, *sess_out = NULL;
const char *msg = NULL;
int only_12 = 0;
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-connect") == 0 && i + 1 < argc) {
char buf[256]; snprintf(buf, sizeof(buf), "%s", argv[++i]);
char *p = strchr(buf, ':');
if (p) { *p = 0; host = strdup(buf); port = atoi(p + 1); }
else host = strdup(buf);
} else if (strcmp(argv[i], "-CAfile") == 0 && i + 1 < argc) {
cafile = argv[++i];
} else if (strcmp(argv[i], "-servername") == 0 && i + 1 < argc) {
sni = argv[++i];
} else if (strcmp(argv[i], "-sess_in") == 0 && i + 1 < argc) {
sess_in = argv[++i];
} else if (strcmp(argv[i], "-sess_out") == 0 && i + 1 < argc) {
sess_out = argv[++i];
} else if (strcmp(argv[i], "-msg") == 0 && i + 1 < argc) {
msg = argv[++i];
} else if (strcmp(argv[i], "-tls1_2") == 0) {
only_12 = 1;
}
}
if (!host) { fprintf(stderr, "need -connect host:port\n"); return 1; }
paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_CLIENT);
if (only_12) paze_tls_config_set_versions(cfg, 0, 1);
if (sni) paze_tls_config_set_sni(cfg, sni);
const char *alpn_default[] = { "http/1.1" };
paze_tls_config_set_alpn(cfg, alpn_default, 1);
if (!cafile) paze_tls_config_set_verify(cfg, PAZE_TLS_VERIFY_NONE, NULL, NULL);
else {
size_t clen; uint8_t *cdata = read_file(cafile, &clen);
if (cdata) {
paze_tls_config_set_trust_roots(cfg, (const char *)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);
if (sock == SOCK_INVALID) { fprintf(stderr, "connect failed\n"); return 1; }
paze_tls_t *tls = paze_tls_new(cfg);
paze_tls_bio_t bio = { &sock, sock_read, sock_write };
paze_tls_set_bio(tls, bio);
paze_status_t st;
for (int retry = 0; retry < 100; retry++) {
st = paze_tls_handshake(tls);
if (st == PAZE_OK) break;
if (st == PAZE_ERR_IO) continue;
if (!paze_tls_is_handshake_done(tls))
fprintf(stderr, "TLS HS err=%d: %s\n", st, paze_tls_last_error(tls));
paze_tls_free(tls); closesocket(sock); paze_tls_config_free(cfg);
return 1;
}
fprintf(stderr, "Connected (TLS %04x, cipher %04x, resumed %s)\n",
(unsigned)paze_tls_get_version(tls),
(unsigned)paze_tls_get_cipher_suite(tls),
paze_tls_session_resumed(tls) ? "YES" : "no");
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");
}
}
uint8_t buf[16384];
if (msg) {
paze_tls_write(tls, (const uint8_t *)msg, strlen(msg));
int n = paze_tls_read(tls, buf, sizeof(buf));
if (n > 0) { fwrite(buf, 1, (size_t)n, stdout); fprintf(stderr, "\n"); }
} else {
/* Simple stdio relay */
for (;;) {
int n = paze_tls_read(tls, buf, sizeof(buf));
if (n > 0) fwrite(buf, 1, (size_t)n, stdout);
if (n <= 0) break;
}
}
paze_tls_close(tls);
paze_tls_free(tls);
closesocket(sock);
paze_tls_config_free(cfg);
fprintf(stderr, "Closed.\n");
return 0;
}
/* genrsa */
if (strcmp(cmd, "genrsa") == 0) {
const char *outf = NULL; int bits = 2048;
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-out") == 0 && i + 1 < argc) outf = argv[++i];
else bits = atoi(argv[i]);
}
if (!outf) { fprintf(stderr, "need -out\n"); return 1; }
paze_rsa_priv_t k;
paze_status_t st = paze_rsa_gen(&k, bits);
if (st != PAZE_OK) { fprintf(stderr, "RSA gen failed\n"); return 1; }
uint8_t der[4096]; size_t dlen = sizeof(der);
st = paze_rsa_priv_to_der(&k, der, &dlen);
if (st != PAZE_OK) { fprintf(stderr, "DER encode failed\n"); return 1; }
char pem[8192];
size_t plen = paze_pem_encode("RSA PRIVATE KEY", der, dlen, pem);
FILE *f = fopen(outf, "w");
if (!f) { fprintf(stderr, "open %s failed\n", outf); return 1; }
fwrite(pem, 1, plen, f); fclose(f);
fprintf(stderr, "RSA %d-bit key -> %s\n", bits, outf);
return 0;
}
/* verify */
if (strcmp(cmd, "verify") == 0) {
const char *cafile = NULL; const char *certf = NULL;
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-CAfile") == 0 && i + 1 < argc) cafile = argv[++i];
else certf = argv[i];
}
if (!cafile || !certf) { fprintf(stderr, "usage: pazessl verify -CAfile ca.pem cert.pem\n"); return 1; }
size_t clen, alen;
uint8_t *cd = read_file(certf, &clen);
uint8_t *ad = read_file(cafile, &alen);
if (!cd || !ad) { fprintf(stderr, "read file failed\n"); return 1; }
paze_x509_cert_t cert, anchor;
paze_status_t st = paze_x509_load_pem(&cert, (const char *)cd);
if (st != PAZE_OK) { fprintf(stderr, "cert parse failed\n"); return 1; }
st = paze_x509_load_pem(&anchor, (const char *)ad);
if (st != PAZE_OK) { fprintf(stderr, "CA parse failed\n"); return 1; }
st = paze_x509_verify_chain(&cert, 1, &anchor, 1, (int64_t)time(NULL));
fprintf(stderr, "%s\n", st == PAZE_OK ? "OK" : "FAIL");
paze_x509_free(&cert); paze_x509_free(&anchor);
free(cd); free(ad);
return st == PAZE_OK ? 0 : 1;
}
/* x509 */
if (strcmp(cmd, "x509") == 0) {
const char *inf = NULL; int text = 0; int noout = 0;
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-in") == 0 && i + 1 < argc) inf = argv[++i];
else if (strcmp(argv[i], "-text") == 0) text = 1;
else if (strcmp(argv[i], "-noout") == 0) noout = 1;
}
if (!inf) { fprintf(stderr, "need -in cert.pem\n"); return 1; }
size_t clen;
uint8_t *cd = read_file(inf, &clen);
if (!cd) { fprintf(stderr, "read failed\n"); return 1; }
paze_x509_cert_t cert;
paze_status_t st = paze_x509_load_pem(&cert, (const char *)cd);
if (st != PAZE_OK) { fprintf(stderr, "parse failed\n"); return 1; }
if (text) {
fprintf(stderr, "Subject DN len=%zu\n", cert.subject_dn_len);
for (int i = 0; i < cert.san_dns_count; i++)
fprintf(stderr, "SAN: %s\n", cert.san_dns[i]);
}
if (!noout) {
char line[1024];
paze_base64_encode((const uint8_t *)cd, clen, line);
fprintf(stdout, "%s\n", line);
}
paze_x509_free(&cert); free(cd);
return 0;
}
/* req (self-signed cert) */
if (strcmp(cmd, "req") == 0) {
const char *keyout = NULL; const char *outf = NULL;
int days = 365; int bits = 2048; const char *subj = "/CN=localhost";
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-keyout") == 0 && i + 1 < argc) keyout = argv[++i];
else if (strcmp(argv[i], "-out") == 0 && i + 1 < argc) outf = argv[++i];
else if (strcmp(argv[i], "-days") == 0 && i + 1 < argc) days = atoi(argv[++i]);
else if (strcmp(argv[i], "-subj") == 0 && i + 1 < argc) subj = argv[++i];
else if (strcmp(argv[i], "-newkey") == 0 && i + 1 < argc) {
char buf[32]; snprintf(buf, sizeof(buf), "%s", argv[++i]);
if (strstr(buf, "rsa:")) bits = atoi(strchr(buf, ':') + 1);
}
}
if (!keyout || !outf) { fprintf(stderr, "need -keyout and -out\n"); return 1; }
const char *cn = subj;
if (strstr(subj, "/CN=")) cn = strstr(subj, "/CN=") + 4;
paze_rsa_priv_t rsa;
paze_status_t st = paze_rsa_gen(&rsa, bits);
if (st != PAZE_OK) { fprintf(stderr, "RSA gen failed\n"); return 1; }
paze_x509_signer_t signer;
signer.kind = PAZE_X509_PK_RSA;
signer.rsa = rsa;
int64_t nb = (int64_t)time(NULL);
int64_t na = nb + days * 86400;
uint8_t cert_der[4096]; size_t clen = sizeof(cert_der);
st = paze_x509_make_self_signed(cert_der, &clen, &signer, cn, nb, na);
if (st != PAZE_OK) { fprintf(stderr, "cert gen failed\n"); return 1; }
char cpem[8192]; size_t plen = paze_pem_encode("CERTIFICATE", cert_der, clen, cpem);
FILE *fc = fopen(outf, "w");
if (!fc) { fprintf(stderr, "write cert failed\n"); return 1; }
fwrite(cpem, 1, plen, fc); fclose(fc);
uint8_t kder[4096]; size_t kdlen = sizeof(kder);
paze_rsa_priv_to_der(&rsa, kder, &kdlen);
char kpem[8192]; size_t kplen = paze_pem_encode("RSA PRIVATE KEY", kder, kdlen, kpem);
FILE *fk = fopen(keyout, "w");
if (!fk) { fprintf(stderr, "write key failed\n"); return 1; }
fwrite(kpem, 1, kplen, fk); fclose(fk);
fprintf(stderr, "Generated %d-bit RSA key + X.509 cert\n", bits);
return 0;
}
/* s_server */
if (strcmp(cmd, "s_server") == 0) {
int port = 4433;
const char *certf = NULL; const char *keyf = NULL;
int only_12 = 0;
int reuse = 1;
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-accept") == 0 && i + 1 < argc) port = atoi(argv[++i]);
else if (strcmp(argv[i], "-cert") == 0 && i + 1 < argc) certf = argv[++i];
else if (strcmp(argv[i], "-key") == 0 && i + 1 < argc) keyf = argv[++i];
else if (strcmp(argv[i], "-reuse") == 0 && i + 1 < argc) reuse = atoi(argv[++i]);
else if (strcmp(argv[i], "-tls1_2") == 0) only_12 = 1;
}
if (!certf || !keyf) {
fprintf(stderr, "s_server needs -cert and -key\n");
fprintf(stderr, "You can generate with: pazessl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -subj /CN=localhost\n");
return 1;
}
size_t clen, klen;
uint8_t *cdata = read_file(certf, &clen);
uint8_t *kdata = read_file(keyf, &klen);
if (!cdata || !kdata) { fprintf(stderr, "read cert/key failed\n"); return 1; }
/* Setup TLS config */
paze_tls_config_t *cfg = paze_tls_config_new(PAZE_TLS_SERVER);
if (only_12) paze_tls_config_set_versions(cfg, 0, 1);
paze_tls_config_set_cert_chain(cfg, (const char *)cdata, (const char *)kdata);
paze_tls_config_set_verify(cfg, PAZE_TLS_VERIFY_NONE, NULL, NULL);
/* Listen */
#ifdef _WIN32
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
#endif
sock_t lsock = socket(AF_INET, SOCK_STREAM, 0);
if (lsock == SOCK_INVALID) { fprintf(stderr, "socket failed\n"); return 1; }
int opt = 1;
setsockopt(lsock, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(lsock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
fprintf(stderr, "bind port %d failed\n", port);
closesocket(lsock); return 1;
}
if (listen(lsock, 4) < 0) {
fprintf(stderr, "listen failed\n");
closesocket(lsock); return 1;
}
fprintf(stderr, "Listening on port %d (-reuse %d)...\n", port, reuse);
int overall = 0;
for (int conn_idx = 0; conn_idx < reuse; conn_idx++) {
struct sockaddr_in client;
socklen_t cl = sizeof(client);
sock_t conn = accept(lsock, (struct sockaddr *)&client, &cl);
if (conn == SOCK_INVALID) { closesocket(lsock); return 1; }
fprintf(stderr, "Client #%d connected\n", conn_idx + 1);
paze_tls_t *tls = paze_tls_new(cfg);
paze_tls_bio_t bio = { &conn, sock_read, sock_write };
paze_tls_set_bio(tls, bio);
paze_status_t st = paze_tls_handshake(tls);
if (st != PAZE_OK) {
fprintf(stderr, "TLS handshake failed: %s\n", paze_tls_last_error(tls));
} else {
fprintf(stderr, "TLS handshake OK (version %04x, cipher %04x, resumed %s)\n",
(unsigned)paze_tls_get_version(tls),
(unsigned)paze_tls_get_cipher_suite(tls),
paze_tls_session_resumed(tls) ? "YES" : "no");
/* 跨连接共享 session ticket key,使后续连接可恢复会话 */
uint8_t tk[32]; int tk_ready = 0;
paze_tls_get_session_ticket_key(tls, tk, &tk_ready);
if (tk_ready) paze_tls_config_set_session_ticket_key(cfg, tk);
/* Echo loop */
uint8_t buf[4096];
for (int i = 0; i < 100; i++) {
int n = paze_tls_read(tls, buf, sizeof(buf));
if (n <= 0) break;
paze_tls_write(tls, buf, (size_t)n);
}
overall = 0;
}
paze_tls_close(tls);
paze_tls_free(tls);
closesocket(conn);
if (st != PAZE_OK) overall = 1;
}
closesocket(lsock);
paze_tls_config_free(cfg);
free(cdata); free(kdata);
return overall;
}
fprintf(stderr, "pazessl %s: not yet implemented\n", cmd);
return 1;
}