Add optional one-way TLS on the device port (6543), enabled per client via a pinned trust anchor, so new clients encrypt their transport while legacy plaintext clients keep working unchanged. Server (server/go): sniff each accepted connection and wrap TLS peers (ClientHello 0x16 0x03 + handshake type 0x01) in crypto/tls with MinVersion TLS 1.2; everything else stays on the plaintext path. TLS is enabled only when both YAMA_TLS_CERT and YAMA_TLS_KEY are set, and a cert/key load failure fails startup loudly rather than silently serving plaintext. Client (client/, Windows): Schannel one-way TLS. When the trust anchor is present — registry value Software\<exehash>\settings\tls_cert (REG_SZ holding the PEM), with fallback to settings.ini [settings] tls_cert or yama-ca.pem beside the exe — every outbound connection (main + sub-connections) completes a TLS 1.2 handshake before the app protocol and is strictly pinned to that anchor via a chain engine with hExclusiveRoot; any handshake/cert failure is fail-closed (no plaintext downgrade). Send uses EncryptMessage; Recv uses DecryptMessage and reads plaintext from the stream-mode DATA segment (bufs[1]) rather than the record header. Co-Authored-By: deepseek-v4-pro
89 lines
4.0 KiB
C++
89 lines
4.0 KiB
C++
// TlsChannel.h — Windows 客户端单向 TLS(Schannel)会话封装
|
||
//
|
||
// 用途:为 YAMA 客户端(被控端)提供「按需开启」的传输层加密。当信任锚证书存在时
|
||
// (注册表 Software\<exehash>\settings\tls_cert,或 yama-ca.pem 文件,见 TlsChannel.cpp
|
||
// 的 InitTls),所有出站 TCP 连接在应用协议首包前先完成 TLS 握手;握手失败一律断开
|
||
// (fail-closed,不回退明文)。
|
||
//
|
||
// 选型:Schannel(Windows 原生 SSPI),零第三方依赖;secur32.lib / crypt32.lib 为
|
||
// 系统库。单向 TLS:客户端只校验服务端证书链到固定的信任锚,不提供客户端证书,
|
||
// 客户端身份仍由隧道内登录(V1 PwdHmac / V2 ECDSA)承担。
|
||
//
|
||
// 线程模型:Handshake 在连接建立线程上、m_bConnected 置位前调用;Send/Recv 分别在
|
||
// 发送线程 / 接收线程上调用。上下文生命周期(Handshake/CloseHandles 释放 m_ctx)与
|
||
// Send/Recv 对 m_ctx 的使用(EncryptMessage/DecryptMessage)由 m_ctxMtx 互斥,避免
|
||
// 重连线程释放上下文时与使用线程产生 use-after-free。本对象作为 IOCPClient 的值成员
|
||
// 存在,析构时释放上下文。
|
||
//
|
||
// 注意:本文件仅在 _WIN32 下有效,非 Windows 构建(Linux/macOS/Android)不含任何内容,
|
||
// 客户端维持明文(TLS 接入按设计文档延后)。
|
||
#pragma once
|
||
|
||
#ifdef _WIN32
|
||
|
||
#include <WinSock2.h>
|
||
|
||
#ifndef SECURITY_WIN32
|
||
#define SECURITY_WIN32 1
|
||
#endif
|
||
#include <security.h>
|
||
#include <schannel.h>
|
||
|
||
#include <mutex>
|
||
#include <vector>
|
||
|
||
// 进程级 TLS 开关:首次调用时加载信任锚(注册表或文件),返回是否启用。线程安全、幂等。
|
||
bool TlsEnabled();
|
||
|
||
// 信任锚已配置但加载失败(损坏/格式错误)时为 true:表示管理员已「颁发」证书,
|
||
// 客户端必须拒绝连接(fail-closed),不得静默回退明文。线程安全、幂等。
|
||
bool TlsLoadError();
|
||
|
||
class TlsChannel
|
||
{
|
||
public:
|
||
TlsChannel() {}
|
||
~TlsChannel() { CloseHandles(); }
|
||
|
||
// 持 Schannel 句柄,禁止拷贝/赋值(避免句柄被二次释放)。
|
||
TlsChannel(const TlsChannel&) = delete;
|
||
TlsChannel& operator=(const TlsChannel&) = delete;
|
||
|
||
// 在已连通的 socket 上完成 TLS 客户端握手并校验服务端证书链。失败返回 false,
|
||
// 调用方必须关闭 socket(fail-closed)。可重复调用(重连时复用同一对象)。
|
||
bool Handshake(SOCKET sock);
|
||
|
||
// 解密读取:返回 >0 的明文字节数;0 表示对端正常关闭;SOCKET_ERROR(-1) 出错。
|
||
int Recv(SOCKET sock, char* buf, int len);
|
||
|
||
// 加密发送 len 字节:成功返回已完整发送的明文字节数(== len);失败返回已发送的
|
||
// 前缀字节数(>0)或 SOCKET_ERROR(一个字节都没发出去时)。
|
||
int Send(SOCKET sock, const char* buf, int len);
|
||
|
||
// 是否有已解密但尚未交付给应用层的明文(供接收线程在 select 前优先消费)。
|
||
bool HasPending() const { return !m_pending.empty(); }
|
||
|
||
private:
|
||
void CloseHandles();
|
||
bool SendAll(SOCKET sock, const void* data, int len);
|
||
bool VerifyServerCert();
|
||
|
||
CredHandle m_cred = {};
|
||
CtxtHandle m_ctx = {};
|
||
bool m_credValid = false; // m_cred 已分配,需 FreeCredentialsHandle
|
||
bool m_ctxValid = false; // m_ctx 已分配,需 DeleteSecurityContext
|
||
bool m_ready = false; // 握手完成,Send/Recv 可用
|
||
|
||
SecPkgContext_StreamSizes m_sizes = {};
|
||
|
||
// 保护上下文生命周期:重连线程的 Handshake/CloseHandles(释放 m_ctx)与工作线程的
|
||
// Recv(DecryptMessage)、发送线程的 Send(EncryptMessage)互斥,避免 use-after-free。
|
||
// recursive 是因为 Handshake 内部会再调 CloseHandles。
|
||
std::recursive_mutex m_ctxMtx;
|
||
|
||
std::vector<BYTE> m_raw; // 尚未解密 / 未消费完的 TLS 记录原始字节
|
||
std::vector<BYTE> m_pending; // 已解密、未交付给应用层的明文
|
||
};
|
||
|
||
#endif // _WIN32
|