Files
yuanyuanxianganddeepseek-v4-pro 96b9eea70a 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
2026-10-15 13:48:27 +02:00

605 lines
22 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ServerTlsSession.cpp — 服务端单向 TLS(Schannel 入站)实现
//
// 与客户端 TlsChannel.cpp 对称,但走 AcceptSecurityContext 入站握手、DecryptMessage/
// EncryptMessage 流式加解密。证书/私钥加载为 PEM + CNG(NCryptImportKey PKCS#8),
// 以便与 Go 服务端(server/go 的 crypto/tls,YAMA_TLS_CERT/KEY)共用同一对密钥。
#include "StdAfx.h"
#include "ServerTlsSession.h"
#include "Server.h" // CONTEXT_OBJECT::FreeTls()
#include "common/iniFile.h" // config(GetInt/GetStr)
#include <ncrypt.h>
#include <bcrypt.h>
#include <fstream>
#include <iterator>
#include <sstream>
#pragma comment(lib, "secur32.lib")
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "ncrypt.lib")
namespace
{
// 进程级身份(进程启动加载一次;未来 UI/指令热切换时改为引用计数 + 惰性释放)。
std::mutex g_identityMtx;
bool g_enabled = false;
bool g_ready = false;
bool g_loadError = false;
CredHandle g_cred = {};
bool g_credValid = false;
PCCERT_CONTEXT g_cert = NULL; // 与入站凭据同生命周期,FreeIdentity 时释放
NCRYPT_KEY_HANDLE g_persistedKey = 0; // 已持久化到 CNG KSP 的私钥句柄,FreeIdentity 时删除
// 持久化私钥在 CNG KSP 中的名字。用固定名 + NCRYPT_OVERWRITE_KEY_FLAG,重启时覆盖旧键,
// 避免残留;同名键的密钥材料与 server.key 相同,覆盖无副作用。
const wchar_t* g_keyName = L"YamaServerTlsKey";
// 一次性读文件为 std::string(二进制安全)。
bool ReadFileText(const std::string& path, std::string& out)
{
std::ifstream f(path, std::ios::binary);
if (!f.is_open())
return false;
std::ostringstream ss;
ss << f.rdbuf();
if (f.bad())
return false;
out = ss.str();
return true;
}
// PEM(-----BEGIN ...----- 包裹的 base64)解码为 DER。失败返回 false。
// CRYPT_STRING_ANY 会自动识别带头的 base64(PEM),对证书与 PKCS#8 私钥均适用。
bool PemToDer(const std::string& pem, std::vector<BYTE>& der)
{
if (pem.empty())
return false;
DWORD need = 0;
if (!CryptStringToBinaryA(pem.data(), (DWORD)pem.size(), CRYPT_STRING_ANY,
NULL, &need, NULL, NULL))
return false;
der.resize(need);
return CryptStringToBinaryA(pem.data(), (DWORD)pem.size(), CRYPT_STRING_ANY,
der.data(), &need, NULL, NULL) == TRUE;
}
// 从 PEM 文本里定位私钥块的类型,用于给出可操作的错误提示。
enum KeyFmt { KF_PKCS8, KF_PKCS1, KF_UNKNOWN };
KeyFmt DetectKeyFormat(const std::string& keyPem)
{
if (keyPem.find("-----BEGIN PRIVATE KEY-----") != std::string::npos)
return KF_PKCS8;
if (keyPem.find("-----BEGIN RSA PRIVATE KEY-----") != std::string::npos ||
keyPem.find("-----BEGIN EC PRIVATE KEY-----") != std::string::npos)
return KF_PKCS1;
return KF_UNKNOWN;
}
}
// =====================================================================================
// ServerTlsIdentity —— 进程级服务端身份(可重入加载,禁止 call_once,供未来热切换)
// =====================================================================================
void ServerTlsIdentity::FreeIdentity()
{
// 先释放凭据,再释放证书上下文,最后删除持久化私钥(凭据/证书可能仍按名引用该键)。
if (g_credValid)
{
FreeCredentialsHandle(&g_cred);
g_credValid = false;
}
g_cred = {};
if (g_cert)
{
CertFreeCertificateContext(g_cert);
g_cert = NULL;
}
if (g_persistedKey)
{
NCryptDeleteKey(g_persistedKey, 0); // 删除并释放句柄
g_persistedKey = 0;
}
}
bool ServerTlsIdentity::Load(config& cfg)
{
std::lock_guard<std::mutex> lk(g_identityMtx);
bool enabled = cfg.GetInt("tls", "Enabled", 0) != 0;
std::string certPath = cfg.GetStr("tls", "CertPath", "");
std::string keyPath = cfg.GetStr("tls", "KeyPath", "");
g_enabled = enabled;
// 先释放旧身份(本轮只在启动时调用一次,不存在「旧身份仍被并发握手使用」的问题;
// 未来热切换需先引用计数、再惰性释放旧凭据)。
FreeIdentity();
g_ready = false;
g_loadError = false;
if (!enabled)
{
Mprintf("[TLS] server TLS disabled ([tls] Enabled=0) — plaintext only\n");
return true;
}
if (certPath.empty() || keyPath.empty())
{
g_loadError = true;
Mprintf("[TLS] FATAL: [tls] Enabled=1 but CertPath/KeyPath empty; "
"refusing TLS ClientHello (fail-closed)\n");
return false;
}
if (!LoadIdentity(certPath, keyPath))
{
g_loadError = true;
return false;
}
g_ready = true;
Mprintf("[TLS] server TLS enabled: cert=%s key=%s\n", certPath.c_str(), keyPath.c_str());
return true;
}
bool ServerTlsIdentity::LoadIdentity(const std::string& certPath, const std::string& keyPath)
{
std::string certPem, keyPem;
if (!ReadFileText(certPath, certPem))
{
Mprintf("[TLS] FATAL: cannot read cert file %s\n", certPath.c_str());
return false;
}
if (!ReadFileText(keyPath, keyPem))
{
Mprintf("[TLS] FATAL: cannot read key file %s\n", keyPath.c_str());
return false;
}
// 1) 证书:PEM → DER → PCCERT_CONTEXT。
std::vector<BYTE> certDer;
if (!PemToDer(certPem, certDer))
{
Mprintf("[TLS] FATAL: cert file %s is not a valid PEM\n", certPath.c_str());
return false;
}
PCCERT_CONTEXT cert = CertCreateCertificateContext(X509_ASN_ENCODING,
certDer.data(), (DWORD)certDer.size());
if (cert == NULL)
{
Mprintf("[TLS] FATAL: CertCreateCertificateContext failed (%u)\n", GetLastError());
return false;
}
// 2) 私钥:PEM → DER → 持久化到 CNG KSP(仅支持 PKCS#8;PKCS#1 给出可操作的转换提示)。
//
// 关键:Schannel 入站凭据(AcquireCredentialsHandle)需要「能按名重开」的持久化私钥。
// NCryptImportKey 产出的是匿名临时键,绑定 CERT_NCRYPT_KEY_HANDLE_PROP_ID 后 Schannel 仍
// 拿不到(AcquireCredentialsHandle 返回 SEC_E_NO_CREDENTIALS 0x8009030e)。因此先把
// PKCS#8 导入为临时键、导出为裸 RSA 私钥 BLOB(NCryptSetProperty 只认裸 BLOB,不认
// PKCS#8),再写入持久化键并 finalize,最后用 CERT_KEY_PROV_INFO_PROP_ID 按名引用。
std::vector<BYTE> keyDer;
NCRYPT_KEY_HANDLE persistedKey = 0;
if (DetectKeyFormat(keyPem) == KF_PKCS8)
{
if (!PemToDer(keyPem, keyDer))
{
Mprintf("[TLS] FATAL: key file %s is not a valid PEM\n", keyPath.c_str());
CertFreeCertificateContext(cert);
return false;
}
// 显式打开 Microsoft Software Key Storage Provider。hProvider 传 NULL 会让
// NCryptImportKey 走「默认提供者」解析,实测返回 NTE_INVALID_HANDLE(0x80090026),
// 故必须显式取得 KSP 句柄(临时键独立于 hProv,导入后即可释放)。
NCRYPT_PROV_HANDLE hProv = 0;
SECURITY_STATUS st = NCryptOpenStorageProvider(&hProv, MS_KEY_STORAGE_PROVIDER, 0);
if (st != ERROR_SUCCESS)
{
Mprintf("[TLS] FATAL: NCryptOpenStorageProvider failed 0x%08x\n", (unsigned)st);
CertFreeCertificateContext(cert);
return false;
}
// 2a) PKCS#8 → 临时键。
NCRYPT_KEY_HANDLE tmp = 0;
st = NCryptImportKey(hProv, NULL, NCRYPT_PKCS8_PRIVATE_KEY_BLOB, NULL,
&tmp, keyDer.data(), (DWORD)keyDer.size(), 0);
if (st == ERROR_SUCCESS)
{
// 2b) 允许明文导出,导出为裸 RSA 私钥 BLOB。
DWORD policy = NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG | NCRYPT_ALLOW_EXPORT_FLAG;
NCryptSetProperty(tmp, NCRYPT_EXPORT_POLICY_PROPERTY, (PBYTE)&policy, sizeof(policy), 0);
std::vector<BYTE> raw;
DWORD rawLen = 0;
st = NCryptExportKey(tmp, NULL, BCRYPT_RSAFULLPRIVATE_BLOB, NULL, NULL, 0, &rawLen, 0);
if (st == ERROR_SUCCESS)
{
raw.resize(rawLen);
st = NCryptExportKey(tmp, NULL, BCRYPT_RSAFULLPRIVATE_BLOB, NULL,
raw.data(), rawLen, &rawLen, 0);
}
// 2c) 写入持久化键并 finalize(落到 KSP,可被 Schannel 按名重开)。
if (st == ERROR_SUCCESS)
st = NCryptCreatePersistedKey(hProv, &persistedKey, BCRYPT_RSA_ALGORITHM,
g_keyName, AT_KEYEXCHANGE, NCRYPT_OVERWRITE_KEY_FLAG);
if (st == ERROR_SUCCESS)
st = NCryptSetProperty(persistedKey, BCRYPT_RSAFULLPRIVATE_BLOB,
raw.data(), (DWORD)raw.size(), 0);
if (st == ERROR_SUCCESS)
st = NCryptFinalizeKey(persistedKey, 0);
NCryptFreeObject(tmp);
}
NCryptFreeObject(hProv);
if (st != ERROR_SUCCESS)
{
Mprintf("[TLS] FATAL: failed to persist PKCS#8 key for %s (0x%08x)\n",
keyPath.c_str(), (unsigned)st);
if (persistedKey)
NCryptFreeObject(persistedKey);
CertFreeCertificateContext(cert);
return false;
}
}
else
{
// PKCS#1(RSA/EC PRIVATE KEY)或未知格式:本次不支持,给转换命令。
Mprintf("[TLS] FATAL: key file %s must be PKCS#8 (-----BEGIN PRIVATE KEY-----). "
"Convert with: openssl pkcs8 -topk8 -nocrypt -in %s -out %s\n",
keyPath.c_str(), keyPath.c_str(), keyPath.c_str());
CertFreeCertificateContext(cert);
return false;
}
// 3) 绑定私钥到证书上下文:按名引用持久化键。Schannel 经 CryptAcquireCertificatePrivateKey
// 在 MS_KEY_STORAGE_PROVIDER 里用 pwszContainerName 重开该键。
CRYPT_KEY_PROV_INFO provInfo = {};
provInfo.pwszContainerName = (LPWSTR)g_keyName;
provInfo.pwszProvName = (LPWSTR)MS_KEY_STORAGE_PROVIDER;
provInfo.dwProvType = 0; // 0 = CNG(无 CAPI provider type)
provInfo.dwKeySpec = AT_KEYEXCHANGE;
if (!CertSetCertificateContextProperty(cert, CERT_KEY_PROV_INFO_PROP_ID, 0, &provInfo))
{
Mprintf("[TLS] FATAL: CertSetCertificateContextProperty(CERT_KEY_PROV_INFO_PROP_ID) "
"failed (%u)\n", GetLastError());
NCryptDeleteKey(persistedKey, 0); // 删除并释放句柄
CertFreeCertificateContext(cert);
return false;
}
// 绑定后持久化键句柄交进程级 g_persistedKey,随 FreeIdentity 删除(覆盖旧键,避免残留)。
g_persistedKey = persistedKey;
// 4) 入站凭据:显式指定服务端证书,不请求客户端证书(单向 TLS)。
SCHANNEL_CRED sc = {};
sc.dwVersion = SCHANNEL_CRED_VERSION;
sc.cCreds = 1;
sc.paCred = &cert;
sc.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER;
// NO_DEFAULT_CREDS 避免回退到系统默认凭据,确保只用我们提供的证书/私钥。
//(单向 TLS 不请求客户端证书,故无需 SCH_CRED_MANUAL_CRED_VALIDATION。)
sc.dwFlags = SCH_CRED_NO_DEFAULT_CREDS;
TimeStamp tsExpiry = {};
SECURITY_STATUS st = AcquireCredentialsHandleA(
NULL, (SEC_CHAR*)"Microsoft Unified Security Protocol Provider", SECPKG_CRED_INBOUND,
NULL, &sc, NULL, NULL, &g_cred, &tsExpiry);
if (st != SEC_E_OK)
{
Mprintf("[TLS] FATAL: AcquireCredentialsHandle(SECPKG_CRED_INBOUND) failed 0x%08x\n",
(unsigned)st);
CertFreeCertificateContext(cert); // 无凭据引用,安全释放
g_cred = {};
if (g_persistedKey) // 回滚:删除已持久化的私钥,避免残留
{
NCryptDeleteKey(g_persistedKey, 0); // 删除并释放句柄
g_persistedKey = 0;
}
return false;
}
// 成功:把证书上下文生命周期交给进程级 g_cert,随 FreeIdentity 释放,
// 保证在 FreeCredentialsHandle 之前私钥一直可用。
g_cert = cert;
g_credValid = true;
return true;
}
bool ServerTlsIdentity::IsEnabled()
{
std::lock_guard<std::mutex> lk(g_identityMtx);
return g_enabled;
}
bool ServerTlsIdentity::IsReady()
{
std::lock_guard<std::mutex> lk(g_identityMtx);
return g_ready && g_credValid;
}
bool ServerTlsIdentity::IsLoadError()
{
std::lock_guard<std::mutex> lk(g_identityMtx);
return g_loadError;
}
CredHandle ServerTlsIdentity::Cred()
{
std::lock_guard<std::mutex> lk(g_identityMtx);
return g_cred;
}
// =====================================================================================
// ServerTlsSession —— 单连接会话(握手 + 加解密)
// =====================================================================================
void ServerTlsSession::CloseHandles()
{
if (m_ctxValid)
{
DeleteSecurityContext(&m_ctx);
m_ctxValid = false;
}
m_ctx = {};
m_ready = false;
m_raw.clear();
}
// 握手线程在 IOCP 的子套接字上做同步 I/O。该套接字可能是非阻塞的(监听套接字经
// WSAEventSelect 置为非阻塞后,accept 出的子套接字继承非阻塞态;线程里 FIONBIO=0 也
// 不一定即时生效)。故 recv/send 遇 WSAEWOULDBLOCK 时用 select 等可读/可写再重试,
// 而非当致命错误,避免「客户端回包还没到就误判握手失败」。
static bool WaitSocketReady(SOCKET sock, bool forRead, int timeoutMs)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(sock, &fds);
timeval tv;
tv.tv_sec = timeoutMs / 1000;
tv.tv_usec = (timeoutMs % 1000) * 1000;
return select(0, forRead ? &fds : NULL, forRead ? NULL : &fds, NULL, &tv) > 0;
}
bool ServerTlsSession::SendAll(SOCKET sock, const void* data, int len)
{
const BYTE* p = (const BYTE*)data;
int sent = 0;
while (sent < len)
{
int n = send(sock, (const char*)p + sent, len - sent, 0);
if (n == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
{
if (!WaitSocketReady(sock, false, 15000))
return false;
continue; // 非阻塞套接字发送缓冲满:等可写后重试
}
if (n <= 0)
return false;
sent += n;
}
return true;
}
bool ServerTlsSession::Handshake(SOCKET sock, const BYTE* initial, int initialLen)
{
std::lock_guard<std::mutex> lk(m_mtx);
if (m_ctxValid)
CloseHandles(); // 防御:会话应只握手一次
CredHandle cred = ServerTlsIdentity::Cred();
// 单向 TLS:服务端不请求客户端证书,故无需 ASC_REQ_MANUAL_CRED_VALIDATION
//(该标志只在 mTLS 校验客户端证书时有意义;SDK 的 ASC 命名空间里亦无此常量)。
DWORD req = ASC_REQ_SEQUENCE_DETECT | ASC_REQ_REPLAY_DETECT | ASC_REQ_CONFIDENTIALITY |
ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_STREAM;
std::vector<BYTE> hs(initial, initial + initialLen);
CtxtHandle ctx = {};
bool ctxValid = false;
TimeStamp tsExpiry = {};
ULONG attrs = 0;
SECURITY_STATUS sc = SEC_I_CONTINUE_NEEDED;
for (;;)
{
SecBuffer inBufs[2] = {
{ (ULONG)hs.size(), SECBUFFER_TOKEN, hs.empty() ? NULL : hs.data() },
{ 0, SECBUFFER_EMPTY, NULL },
};
SecBufferDesc inDesc = { SECBUFFER_VERSION, 2, inBufs };
SecBuffer outBuf = { 0, SECBUFFER_TOKEN, NULL };
SecBufferDesc outDesc = { SECBUFFER_VERSION, 1, &outBuf };
sc = AcceptSecurityContext(&cred, ctxValid ? &ctx : NULL, &inDesc, req,
SECURITY_NATIVE_DREP, ctxValid ? NULL : &ctx,
&outDesc, &attrs, &tsExpiry);
ctxValid = true;
// 发出服务端握手记录(ServerHello/Certificate/... 或 Finished)。
if (outBuf.cbBuffer > 0 && outBuf.pvBuffer)
{
bool ok = SendAll(sock, outBuf.pvBuffer, (int)outBuf.cbBuffer);
FreeContextBuffer(outBuf.pvBuffer);
if (!ok)
{
sc = SEC_E_INTERNAL_ERROR;
break;
}
}
// 计算已消费字节:SECBUFFER_EXTRA 里是「未消费的尾部」(下一条握手消息 / 首条应用数据)。
// 必须在 SEC_E_OK / 失败判定之前处理:否则握手完成时 hs 里仍混着已消费的握手记录,
// 紧跟 Finished 的首条应用数据(客户端登录包)会随 hs 一起被丢弃,导致随机丢包。
if (inBufs[1].BufferType == SECBUFFER_EXTRA)
{
int extra = (int)inBufs[1].cbBuffer;
hs.erase(hs.begin(), hs.begin() + (hs.size() - extra));
}
else if (sc != SEC_E_INCOMPLETE_MESSAGE && sc != SEC_I_INCOMPLETE_CREDENTIALS)
{
hs.clear(); // 已全部消费,继续等下一轮握手消息
}
// 否则:SEC_E_INCOMPLETE_MESSAGE —— 保留 hs(不完整消息),续收补齐。
if (sc == SEC_E_OK)
break;
if (FAILED(sc) && sc != SEC_E_INCOMPLETE_MESSAGE && sc != SEC_I_INCOMPLETE_CREDENTIALS)
{
Mprintf("[TLS] AcceptSecurityContext failed 0x%08x (in=%u consumed? extra=%u)\n",
(unsigned)sc, inBufs[0].cbBuffer, inBufs[1].BufferType == SECBUFFER_EXTRA ? inBufs[1].cbBuffer : 0);
break; // 真正的失败
}
char raw[8192];
int r;
for (;;)
{
r = recv(sock, raw, sizeof(raw), 0);
if (r == SOCKET_ERROR && WSAGetLastError() == WSAEWOULDBLOCK)
{
if (!WaitSocketReady(sock, true, 15000))
{
r = SOCKET_ERROR;
break; // 等可读超时
}
continue; // 非阻塞套接字暂无数据:等可读后重试
}
break;
}
if (r <= 0)
{
Mprintf("[TLS] handshake recv failed/EOF (r=%d err=%d)\n", r, WSAGetLastError());
sc = SEC_E_INTERNAL_ERROR;
break;
}
hs.insert(hs.end(), (BYTE*)raw, (BYTE*)raw + r);
}
if (sc != SEC_E_OK)
{
Mprintf("[TLS] handshake failed: final status 0x%08x (fail-closed)\n", (unsigned)sc);
if (ctxValid)
DeleteSecurityContext(&ctx);
return false;
}
// 握手成功后 hs 里残留的「未消费尾部」是紧跟 Finished 的首条应用数据(TLS 记录)。
// 交给 Decrypt 处理,否则首包会丢(表现为随机登录超时/重连/协议错位)。
if (!hs.empty())
m_raw.insert(m_raw.end(), hs.begin(), hs.end());
// 查询流式记录尺寸(加密封装用)。
SecPkgContext_StreamSizes sizes = {};
if (QueryContextAttributes(&ctx, SECPKG_ATTR_STREAM_SIZES, &sizes) != SEC_E_OK)
{
DeleteSecurityContext(&ctx);
return false;
}
m_ctx = ctx;
m_ctxValid = true;
m_sizes = sizes;
m_ready = true;
return true;
}
int ServerTlsSession::Decrypt(const BYTE* cipher, int len, std::vector<BYTE>& plain)
{
std::lock_guard<std::mutex> lk(m_mtx);
if (!m_ready)
return -1;
if (len <= 0)
return 0;
m_raw.insert(m_raw.end(), cipher, cipher + len);
for (;;)
{
SecBuffer bufs[4] = {
{ (ULONG)m_raw.size(), SECBUFFER_DATA, m_raw.empty() ? NULL : m_raw.data() },
{ 0, SECBUFFER_EMPTY, NULL },
{ 0, SECBUFFER_EMPTY, NULL },
{ 0, SECBUFFER_EMPTY, NULL },
};
SecBufferDesc desc = { SECBUFFER_VERSION, 4, bufs };
SECURITY_STATUS sc = DecryptMessage(&m_ctx, &desc, 0, NULL);
if (sc == SEC_E_INCOMPLETE_MESSAGE)
break; // 记录不完整,等下一轮 WSARecv
if (sc == SEC_I_CONTEXT_EXPIRED) {
Mprintf("[TLS] DecryptMessage: peer close_notify (SEC_I_CONTEXT_EXPIRED)\n");
return -1; // 对端 close_notify / 会话结束
}
if (sc == SEC_I_RENEGOTIATE || FAILED(sc)) {
Mprintf("[TLS] DecryptMessage failed 0x%08x (raw=%u)\n", (unsigned)sc, (unsigned)m_raw.size());
return -1;
}
int decLen = (int)bufs[1].cbBuffer;
const BYTE* p = (const BYTE*)bufs[1].pvBuffer;
int extra = (bufs[3].BufferType == SECBUFFER_EXTRA) ? (int)bufs[3].cbBuffer : 0;
int consumed = (int)m_raw.size() - extra;
if (decLen > 0 && p)
plain.insert(plain.end(), p, p + decLen);
m_raw.erase(m_raw.begin(), m_raw.begin() + consumed);
if (m_raw.empty())
break;
}
return 0;
}
int ServerTlsSession::Encrypt(const BYTE* plain, int len, std::vector<BYTE>& wire)
{
std::lock_guard<std::mutex> lk(m_mtx);
if (!m_ready)
return -1;
if (len <= 0)
return 0;
ULONG maxMsg = m_sizes.cbMaximumMessage > 0 ? m_sizes.cbMaximumMessage : 16384;
int total = 0;
while (total < len)
{
int chunk = (len - total > (int)maxMsg) ? (int)maxMsg : (len - total);
std::vector<BYTE> rec(m_sizes.cbHeader + chunk + m_sizes.cbTrailer);
BYTE* hdr = rec.data();
BYTE* body = hdr + m_sizes.cbHeader;
BYTE* trailer = body + chunk;
memcpy(body, plain + total, chunk);
SecBuffer bufs[4] = {
{ m_sizes.cbHeader, SECBUFFER_STREAM_HEADER, hdr },
{ (ULONG)chunk, SECBUFFER_DATA, body },
{ m_sizes.cbTrailer, SECBUFFER_STREAM_TRAILER, trailer },
{ 0, SECBUFFER_EMPTY, NULL },
};
SecBufferDesc desc = { SECBUFFER_VERSION, 4, bufs };
SECURITY_STATUS sc = EncryptMessage(&m_ctx, 0, &desc, 0);
if (FAILED(sc))
return -1; // 握手已建立,此处失败视为致命(调用方关闭连接)
int wireLen = (int)(bufs[0].cbBuffer + bufs[1].cbBuffer + bufs[2].cbBuffer);
wire.insert(wire.end(), rec.data(), rec.data() + wireLen);
total += chunk;
}
return total;
}
// =====================================================================================
// CONTEXT_OBJECT::FreeTls —— 释放每连接 TLS 会话(定义在此以便 delete 完整类型)
// =====================================================================================
void CONTEXT_OBJECT::FreeTls()
{
delete tls;
tls = NULL;
}