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

+34 -1
View File
@@ -227,7 +227,11 @@ IOCPClient::IOCPClient(const State&bExit, bool exit_while_disconnect, int mask,
m_DataProcess = NULL;
m_ReconnectFunc = NULL;
m_masker = mask ? new HttpMask(DEFAULT_HOST) : new PkgMask();
#ifdef _DEBUG
auto enc = GetHeaderEncoder(HeaderEncType(time(nullptr) % (HeaderEncV6 + 1)));
#else
auto enc = GetHeaderEncoder(HeaderEncType(time(nullptr) % HeaderEncNum));
#endif
m_EncoderType = encoder;
m_Encoder = encoder ? new HellEncoder(enc, new XOREncoder16()) : new ProtocolEncoder();
#ifdef _WIN32
@@ -463,6 +467,25 @@ BOOL IOCPClient::ConnectServer(const char* szServerIP, unsigned short uPort)
SetKeepAliveOptions(m_sClientSocket);
#endif
}
#ifdef _WIN32
// 单向 TLS:证书存在(TlsEnabled())时,在应用协议首包前完成握手;握手失败即断开,
// 不回退明文(fail-closed,防降级)。证书存在但加载失败(TlsLoadError())同样拒绝
// 连接。握手须在 m_bConnected 置位前,确保工作线程不会在握手尚未完成时就开始 recv。
if (TlsLoadError()) {
Mprintf("[TLS] 证书加载失败,拒绝连接(fail-closed)\n");
closesocket(m_sClientSocket);
m_sClientSocket = INVALID_SOCKET;
return FALSE;
}
if (TlsEnabled()) {
if (!m_tls.Handshake(m_sClientSocket)) {
Mprintf("[TLS] 握手失败,断开连接(fail-closed)\n");
closesocket(m_sClientSocket);
m_sClientSocket = INVALID_SOCKET;
return FALSE;
}
}
#endif
m_bConnected = TRUE;
Mprintf("连接服务端成功: %s:%d.\n", m_sCurIP.c_str(), (int)port);
@@ -526,6 +549,16 @@ DWORD WINAPI IOCPClient::WorkThreadProc(LPVOID lParam)
Sleep(50);
continue;
}
#ifdef _WIN32
// TLS 会话里可能有已解密、尚未交付的明文(select 只看底层 socket,看不到
// Schannel 内部缓冲),优先消费,避免被 select 卡住。
if (This->HasPendingData()) {
if (!This->ProcessRecvData(&m_CompressedBuffer, szBuffer, MAX_RECV_BUFFER - 1, 0)) {
break;
}
continue;
}
#endif
FD_ZERO(&fd);
FD_SET(This->m_sClientSocket, &fd);
// Linux select() 会修改 timeval,必须每次重置
@@ -632,7 +665,7 @@ VOID IOCPClient::OnServerReceiving(CBuffer* m_CompressedBuffer, char* szBuffer,
for (int i = 0; i < 16 && i < (int)bufLen; ++i) {
char tmp[12]; sprintf(tmp, "%02X ", (unsigned char)src[i]); buf += tmp;
}
Mprintf("[ERROR] Unknown header! bufLen=%lu, first 16 bytes: %s\n", bufLen, buf.c_str());
Mprintf("[ERROR] Unknown header! this=%p bufLen=%lu, first 16 bytes: %s\n", this, bufLen, buf.c_str());
m_CompressedBuffer->ClearBuffer();
break;
}