Feat: One-way TLS transport for the MFC server
Add optional one-way TLS to the MFC server (server/2015Remote) so the device port can serve both TLS clients and legacy plaintext clients on the same port, mirroring the Go server. Server: on a connection's first bytes, sniff for a TLS ClientHello (0x16 0x03 + handshake type 0x01). When [tls] Enabled=1 and the identity loads, hand the socket to a short-lived blocking handshake thread (Option A) running Schannel AcceptSecurityContext, then decrypt on recv and encrypt on send; plaintext connections replay the sniffed bytes straight into the existing parse path, so the plaintext code path is otherwise unchanged. The identity is loaded once from [tls] CertPath/KeyPath as PEM + CNG (PKCS#8) — the same key pair the Go server uses — and a missing [tls] section, an empty value, or a load failure leaves plaintext serving intact and fail-closes any TLS ClientHello. Also fix two transport-reliability bugs that surfaced end-to-end: - send path: encrypt only the freshly appended plaintext tail and post a single IOWrite wake-up guarded by a bSendPending flag, so back-to-back small sends (terminal resize -> COMMAND_NEXT -> resize) no longer run concurrent WSASends over the same mutable buffer (client saw DecryptMessage failures, with WSAGetLastError() = 6001 / ERROR_DECRYPTION_FAILED); - client Recv: keep the handshake's unconsumed tail bytes in m_raw and drain them before the next recv, and log the real SECURITY_STATUS on DecryptMessage failure instead of the stale Winsock error. Co-Authored-By: deepseek-v4-pro
This commit is contained in:
1 parent
ca0d7626d2
commit
96b9eea70a
16 files changed
+1298
-38
No files matched your search
+26
-12
@@ -368,6 +368,11 @@ bool TlsChannel::Handshake(SOCKET sock)
|
||||
return false;
|
||||
}
|
||||
|
||||
// 握手成功后 hs 里可能残留「未消费的尾部」= 服务端 Finished 后紧跟的首条应用数据
|
||||
// (TLS 记录)。若不下发,首包会被丢弃,导致随机丢包/协议错位。交给 Recv 解。
|
||||
if (!hs.empty())
|
||||
m_raw.insert(m_raw.end(), hs.begin(), hs.end());
|
||||
|
||||
if (!VerifyServerCert()) {
|
||||
Mprintf("[TLS] server certificate not trusted against yama-ca.pem (fail-closed)\n");
|
||||
CloseHandles();
|
||||
@@ -404,14 +409,10 @@ int TlsChannel::Recv(SOCKET sock, char* out, int outLen)
|
||||
|
||||
char raw[16384];
|
||||
for (;;) {
|
||||
int r = recv(sock, raw, sizeof(raw), 0);
|
||||
if (r == SOCKET_ERROR)
|
||||
return SOCKET_ERROR;
|
||||
if (r == 0)
|
||||
return 0; // 对端关闭(此时无 pending)
|
||||
m_raw.insert(m_raw.end(), (BYTE*)raw, (BYTE*)raw + r);
|
||||
|
||||
for (;;) {
|
||||
// 优先解密 m_raw 里已缓冲的字节(握手时随 Finished 收下的首条应用数据,或上轮
|
||||
// 一次 recv 到多条记录后剩下的记录)。select() 只看底层 socket,看不到这些字节,
|
||||
// 若先 recv 就会把它们卡住(随机延迟/死锁/协议错位)。
|
||||
while (!m_raw.empty()) {
|
||||
SecBuffer bufs[4] = {
|
||||
{ (ULONG)m_raw.size(), SECBUFFER_DATA, m_raw.data() },
|
||||
{ 0, SECBUFFER_EMPTY, NULL },
|
||||
@@ -422,11 +423,17 @@ int TlsChannel::Recv(SOCKET sock, char* out, int outLen)
|
||||
SECURITY_STATUS sc = DecryptMessage(&m_ctx, &desc, 0, NULL);
|
||||
|
||||
if (sc == SEC_E_INCOMPLETE_MESSAGE)
|
||||
break; // 记录不完整,再 recv
|
||||
break; // 记录不完整,去 recv 补字节
|
||||
if (sc == SEC_I_CONTEXT_EXPIRED)
|
||||
return 0; // close_notify
|
||||
if (sc == SEC_I_RENEGOTIATE || FAILED(sc))
|
||||
if (sc == SEC_I_RENEGOTIATE || FAILED(sc)) {
|
||||
// 客户端解密失败(服务端发来的密文损坏/错位)。这里只返回 SOCKET_ERROR,
|
||||
// 而 WSAGetLastError() 会残留上一次 Winsock 调用的旧值(如 6001),
|
||||
// 掩盖真实原因,故把真实的 SECURITY_STATUS 记下来。
|
||||
Mprintf("[TLS] client DecryptMessage failed 0x%08x (raw=%u pending=%u)\n",
|
||||
(unsigned)sc, (unsigned)m_raw.size(), (unsigned)m_pending.size());
|
||||
return SOCKET_ERROR;
|
||||
}
|
||||
|
||||
// 流模式 DecryptMessage 会把输入 SECBUFFER_DATA 重组为 4 段:
|
||||
// bufs[0]=STREAM_HEADER(记录头+nonce)、bufs[1]=DATA(明文)、
|
||||
@@ -445,9 +452,16 @@ int TlsChannel::Recv(SOCKET sock, char* out, int outLen)
|
||||
m_pending.erase(m_pending.begin(), m_pending.begin() + n);
|
||||
return n;
|
||||
}
|
||||
if (m_raw.empty())
|
||||
break; // 无明文(如 alert/CCS 记录),继续 recv
|
||||
// 无明文的记录(如 alert/CCS):继续循环解下一条
|
||||
}
|
||||
|
||||
int r = recv(sock, raw, sizeof(raw), 0);
|
||||
if (r == SOCKET_ERROR)
|
||||
return SOCKET_ERROR;
|
||||
if (r == 0)
|
||||
return 0; // 对端关闭(此时无 pending,且 m_raw 已解完/为空)
|
||||
m_raw.insert(m_raw.end(), (BYTE*)raw, (BYTE*)raw + r);
|
||||
// 循环回顶部,解密刚收到的字节
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user