TLS 1.3 PSK 会话恢复: NewSessionTicket 签发/解析、ticket+binder 校验、selected_identity 回选、恢复握手免证书(pazessl -sess_in/-sess_out 端到端验证); 构建脚本输出 bin/ 并支持独立命令与 psftp; 修复 SFTP 二进制传输与 copy_id -P; 补充 tests/ 调试与 verify_tls
447 行
17 KiB
C
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;
|
|
}
|