Files
SimpleRemoter/server/2015Remote/TlsCertificateDlg.cpp
T
yuanyuanxianganddeepseek-v4-pro c93f09e9ac Feat: TLS certificate menu and config dialog for the MFC server
Add a "TLS证书" item to the Extensions menu below "MCP设置" that opens a
runtime-built dialog to enable one-way TLS and set the certificate and
private-key paths. On OK it writes the [tls] Enabled/CertPath/KeyPath keys
and calls ServerTlsIdentity::Load() so the change takes effect for new
connections immediately (design doc §7.1), mirroring the existing
CMcpSettingsDlg runtime-template pattern. All new strings are wired into
the en_US / zh_TW / de_DE language files, and the menu item is gated by
HIDE_MENU_TLS_CERTIFICATE / MF_TLS_CERTIFICATE like MCP settings.

Co-Authored-By: deepseek-v4-pro
2026-10-15 19:42:52 +02:00

235 lines
8.5 KiB
C++
Raw 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.
#include "stdafx.h"
#include "TlsCertificateDlg.h"
#include "LangManager.h" // _TR / MessageBoxL
#include "2015Remote.h" // THIS_CFG (GetThisCfg)
#include "ServerTlsSession.h" // ServerTlsIdentity::Load / IsLoadError
#include "common/iniFile.h" // config::GetInt/GetStr/SetInt/SetStr
#include <atlconv.h>
#include <afxdlgs.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
namespace {
// 构造一个无控件的 DLGTEMPLATE:cdit=0,控件由 OnInitDialog 动态创建。
// (与 McpSettingsDlg.cpp / ZstaPickerDlg.cpp 内同名函数一致)
void BuildDialogTemplate(std::vector<BYTE>& out, LPCWSTR caption, short cx, short cy)
{
out.clear();
auto append = [&](const void* p, size_t n) {
const BYTE* b = (const BYTE*)p;
out.insert(out.end(), b, b + n);
};
auto appendW = [&](WORD v) {
out.push_back((BYTE)(v & 0xFF));
out.push_back((BYTE)((v >> 8) & 0xFF));
};
auto appendWStr = [&](LPCWSTR s) {
size_t n = wcslen(s);
append(s, (n + 1) * sizeof(WCHAR));
};
DLGTEMPLATE dt = { 0 };
dt.style = DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER |
WS_POPUP | WS_CAPTION | WS_SYSMENU;
dt.dwExtendedStyle = 0;
dt.cdit = 0;
dt.x = 0; dt.y = 0;
dt.cx = cx; dt.cy = cy;
append(&dt, sizeof(dt));
appendW(0); // no menu
appendW(0); // default dialog class
appendWStr(caption); // caption
appendW(8); // font point size
appendWStr(L"MS Shell Dlg"); // typeface
while (out.size() % 4) out.push_back(0); // DWORD align
}
} // namespace
BEGIN_MESSAGE_MAP(CTlsCertificateDlg, CDialog)
ON_BN_CLICKED(IDC_TLS_CERT_BROWSE, &CTlsCertificateDlg::OnBrowseCert)
ON_BN_CLICKED(IDC_TLS_KEY_BROWSE, &CTlsCertificateDlg::OnBrowseKey)
END_MESSAGE_MAP()
CTlsCertificateDlg::CTlsCertificateDlg(CWnd* parent)
: CDialog((LPCTSTR)NULL, parent)
{
}
INT_PTR CTlsCertificateDlg::DoModal()
{
USES_CONVERSION;
CString title = _TR("TLS证书");
BuildDialogTemplate(m_Template, T2CW(title), 340, 180);
InitModalIndirect((LPCDLGTEMPLATE)m_Template.data());
return CDialog::DoModal();
}
BOOL CTlsCertificateDlg::OnInitDialog()
{
CDialog::OnInitDialog();
CRect cli;
GetClientRect(&cli);
// 占位 rect,真正布局在 LayoutControls 里
CRect r0(0, 0, 10, 10);
m_btnEnable.Create(_TR("启用加密"),
WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_AUTOCHECKBOX,
r0, this, IDC_TLS_ENABLE);
m_lblCert.Create(_TR("证书路径"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
m_editCert.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL,
r0, this, IDC_TLS_CERT);
m_btnCertBrowse.Create(_TR("浏览"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
r0, this, IDC_TLS_CERT_BROWSE);
m_lblKey.Create(_TR("私钥路径"), WS_CHILD | WS_VISIBLE, r0, this, (UINT)-1);
m_editKey.Create(WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL,
r0, this, IDC_TLS_KEY);
m_btnKeyBrowse.Create(_TR("浏览"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
r0, this, IDC_TLS_KEY_BROWSE);
m_btnOK.Create(_TR("确定"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON,
r0, this, IDOK);
m_btnCancel.Create(_TR("取消"), WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_PUSHBUTTON,
r0, this, IDCANCEL);
// 子控件继承对话框字体 (DS_SETFONT)
HFONT hFont = (HFONT)::SendMessage(GetSafeHwnd(), WM_GETFONT, 0, 0);
if (hFont) {
m_btnEnable.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_lblCert.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_editCert.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnCertBrowse.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_lblKey.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_editKey.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnKeyBrowse.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnOK.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
m_btnCancel.SendMessage(WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
}
// 回填配置
int enabled = THIS_CFG.GetInt("tls", "Enabled", 0);
std::string cert = THIS_CFG.GetStr("tls", "CertPath", "");
std::string key = THIS_CFG.GetStr("tls", "KeyPath", "");
m_btnEnable.SetCheck(enabled ? BST_CHECKED : BST_UNCHECKED);
m_editCert.SetWindowText(CString(cert.c_str()));
m_editKey.SetWindowText(CString(key.c_str()));
LayoutControls(cli.Width(), cli.Height());
return TRUE;
}
void CTlsCertificateDlg::OnOK()
{
CString sCert, sKey;
m_editCert.GetWindowText(sCert);
m_editKey.GetWindowText(sKey);
bool enabled = (m_btnEnable.GetCheck() == BST_CHECKED);
std::string cert = CT2A(sCert);
std::string key = CT2A(sKey);
// 校验:启用加密时必须填写证书/私钥路径
if (enabled && cert.empty()) {
MessageBoxL("请填写证书路径", "提示", MB_ICONWARNING);
return;
}
if (enabled && key.empty()) {
MessageBoxL("请填写私钥路径", "提示", MB_ICONWARNING);
return;
}
// 落盘 [tls] 三键
THIS_CFG.SetInt("tls", "Enabled", enabled ? 1 : 0);
THIS_CFG.SetStr("tls", "CertPath", cert);
THIS_CFG.SetStr("tls", "KeyPath", key);
// 热重载身份(设计文档 §7.1):开关与证书/私钥路径对新建连接即时生效。
// 已知窄竞态:Load 内部 FreeIdentity 与「已取 Cred 副本、尚在握手」的连接存在竞态,
// 未来热切换需引用计数 + 惰性释放(见 ServerTlsSession.cpp FreeIdentity 注释),本次不做。
ServerTlsIdentity::Load(THIS_CFG);
if (ServerTlsIdentity::IsLoadError()) {
MessageBoxL("证书/私钥加载失败,加密功能未生效。请检查文件路径与格式(证书 PEM、私钥 PKCS#8)。",
"提示", MB_ICONWARNING);
} else {
MessageBox(_TR("TLS 配置已保存。") + _T("\r\n") +
_TR("改动对新建连接即时生效。"),
_TR("提示"), MB_ICONINFORMATION);
}
CDialog::OnOK();
}
void CTlsCertificateDlg::OnBrowseCert()
{
CFileDialog dlg(TRUE, _T("crt"), nullptr,
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
_T("Certificate Files (*.crt;*.pem;*.cer;*.cert)|*.crt;*.pem;*.cer;*.cert|All Files (*.*)|*.*||"),
this);
dlg.m_ofn.lpstrTitle = _T("Select Certificate");
if (dlg.DoModal() == IDOK) {
m_editCert.SetWindowText(dlg.GetPathName());
}
}
void CTlsCertificateDlg::OnBrowseKey()
{
CFileDialog dlg(TRUE, _T("key"), nullptr,
OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
_T("Key Files (*.key;*.pem)|*.key;*.pem|All Files (*.*)|*.*||"),
this);
dlg.m_ofn.lpstrTitle = _T("Select Private Key");
if (dlg.DoModal() == IDOK) {
m_editKey.SetWindowText(dlg.GetPathName());
}
}
void CTlsCertificateDlg::LayoutControls(int cx, int cy)
{
const int margin = 14;
const int labelW = 72;
const int rowH = 24;
const int gap = 10;
const int browseW = 64;
const int btnW = 88;
const int btnH = 26;
int y = margin;
m_btnEnable.MoveWindow(margin, y, cx - margin * 2, 22);
y += 30;
int editW = cx - margin * 2 - labelW - browseW - gap;
m_lblCert.MoveWindow(margin, y, labelW, rowH);
m_editCert.MoveWindow(margin + labelW, y - 2, editW, rowH);
m_btnCertBrowse.MoveWindow(cx - margin - browseW, y - 2, browseW, rowH);
y += rowH + gap;
m_lblKey.MoveWindow(margin, y, labelW, rowH);
m_editKey.MoveWindow(margin + labelW, y - 2, editW, rowH);
m_btnKeyBrowse.MoveWindow(cx - margin - browseW, y - 2, browseW, rowH);
y += rowH + gap;
// 确定 / 取消按钮紧随私钥路径下方
m_btnCancel.MoveWindow(cx - margin - btnW, y, btnW, btnH);
m_btnOK.MoveWindow(cx - margin - btnW * 2 - gap, y, btnW, btnH);
y += btnH + margin;
// 收缩对话框到内容实际高度并重新居中,消除底部空白。
if (y < cy) {
CRect wnd;
GetWindowRect(&wnd);
SetWindowPos(NULL, 0, 0, wnd.Width(), y + (wnd.Height() - cy),
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
CenterWindow();
}
}