Feat: One-way TLS transport for the device port

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
This commit is contained in:
yuanyuanxianganddeepseek-v4-pro committed 2026-10-14 21:10:21 +02:00
1 parent 8d4c118ae6
commit ca0d7626d2
16 files changed
+1418 -5

No files matched your search

+88
View File
@@ -0,0 +1,88 @@
// 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