// TlsChannel.h — Windows 客户端单向 TLS(Schannel)会话封装 // // 用途:为 YAMA 客户端(被控端)提供「按需开启」的传输层加密。当信任锚证书存在时 // (注册表 Software\\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 #ifndef SECURITY_WIN32 #define SECURITY_WIN32 1 #endif #include #include #include #include // 进程级 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); // 是否有待交付的数据:已解密明文(m_pending)或已缓冲但尚未解密的 TLS 记录(m_raw)。 // 供接收线程在 select 前优先消费——select 只看底层 socket,看不到 Schannel 缓冲的 // m_raw(握手时随 Finished 收下的首条应用数据、或一次 recv 到多条记录后的剩余), // 若这里不把 m_raw 也算作「有数据」,这些字节会被 select 卡住。 bool HasPending() const { return !m_pending.empty() || !m_raw.empty(); } // 握手是否已完成(Send/Recv 可用)。诊断用:区分「本连接已走 TLS」与「裸连接」。 bool IsReady() const { return m_ready; } 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 m_raw; // 尚未解密 / 未消费完的 TLS 记录原始字节 std::vector m_pending; // 已解密、未交付给应用层的明文 }; #endif // _WIN32