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
235 lines
8.5 KiB
C++
235 lines
8.5 KiB
C++
#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();
|
||
}
|
||
}
|